From e908181261829199a17477407430da043c6b33f2 Mon Sep 17 00:00:00 2001 From: poc Date: Tue, 15 Apr 2025 12:59:25 +0800 Subject: [PATCH] feat: support iai --- package.json | 6 +- .../plugins/monitor-auth/index.ts | 174 ++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 packages/codemate-plugin/plugins/monitor-auth/index.ts diff --git a/package.json b/package.json index 0c59e7896d..9fb403616d 100644 --- a/package.json +++ b/package.json @@ -74,5 +74,9 @@ "lint-staged": { "*.{js,jsx,ts,tsx,json}": "prettier --write" }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", + "dependencies": { + "qcloud-cos-sts": "^3.1.1", + "tencentcloud-sdk-nodejs-iai": "^4.0.1052" + } } diff --git a/packages/codemate-plugin/plugins/monitor-auth/index.ts b/packages/codemate-plugin/plugins/monitor-auth/index.ts new file mode 100644 index 0000000000..02bf9167a4 --- /dev/null +++ b/packages/codemate-plugin/plugins/monitor-auth/index.ts @@ -0,0 +1,174 @@ +import STS from 'qcloud-cos-sts'; +import { iai } from 'tencentcloud-sdk-nodejs-iai'; +import { ContestModel, ContestNotFoundError, Context, Handler, ObjectId, param, PERM, PRIV, route, SystemModel, Types } from 'hydrooj'; + +const IaiClient = iai.v20200303.Client; +// 定义配置接口 +interface COSConfig { + secretId: string; + secretKey: string; + proxy: string; + durationSeconds: number; + bucket: string; + region: string; + allowPrefix: string; + allowActions: string[]; +} +// 定义临时密钥响应接口 +interface STSCredentials { + credentials: { + tmpSecretId: string; + tmpSecretKey: string; + sessionToken: string; + }; + startTime: number; + expiredTime: number; +} + +const getAuthCredential = async (config: COSConfig) => { + const { allowPrefix, bucket, region, secretId, secretKey, durationSeconds, allowActions } = config; + + const AppId = config.bucket.substr(bucket.lastIndexOf('-') + 1); + + const { credentials, startTime, expiredTime }: STSCredentials = await STS.getCredential({ + secretId, + secretKey, + durationSeconds, + policy: { + version: '2.0', + statement: [ + { + action: allowActions, + effect: 'allow', + resource: [`qcs::cos:${region}:uid/${AppId}:${bucket}/${allowPrefix}`], + }, + ], + }, + }); + + return { + credentials, + startTime, + expiredTime, + bucket, + region, + allowPrefix: allowPrefix.slice(0, -2), + }; +}; + +class CosAuthHandler extends Handler { + @route('contestId', Types.ObjectId) + async get(domainId: string, contestId: ObjectId) { + const config: COSConfig = global.Hydro.lib.cosStsAuthConfig; + const allowPrefix = config.allowPrefix.replace('contestId', contestId.toString()).replace('userId', `${this.user._id}`); + + this.response.body = { + data: await getAuthCredential({ + ...config, + allowPrefix, + }), + }; + } + + @route('contestId', Types.ObjectId) + @param('urlB', Types.String) + async post(domainId: string, contestId: ObjectId, UrlB: string) { + const client = new IaiClient(global.Hydro.lib.cosContestCheckConfig); + const params = { + FaceModelVersion: '3.0', + UrlA: 'https://tts-1325135518.cos.ap-guangzhou.myqcloud.com/contest/contestId/user/userId/face.png' + .replace('contestId', contestId.toString()) + .replace('userId', `${this.user._id}`), + UrlB, + }; + + this.response.body = { + data: await client.CompareFace(params), + }; + } +} + +class CosAuthAdminHandler extends Handler { + @route('contestId', Types.ObjectId) + async get(domainId: string, contestId: ObjectId) { + const contest = await ContestModel.get(domainId, contestId); + if (!this.user.own(contest)) { + throw new ContestNotFoundError(contestId); + } + const config: COSConfig = global.Hydro.lib.cosStsAuthConfig; + const allowPrefix = `${config.allowPrefix.split('/monitor')[0].replace('contestId', contestId.toString())}/*`; + + this.response.body = { + data: await getAuthCredential({ + ...config, + allowPrefix, + allowActions: [ + // 上传操作权限 + 'name/cos:GetBucket', + 'name/cos:HeadObject', + 'name/cos:ListMultipartUploads', + 'name/cos:ListParts', + 'name/cos:PutObject', + 'name/cos:PostObject', + 'name/cos:InitiateMultipartUpload', + 'name/cos:UploadPart', + 'name/cos:CompleteMultipartUpload', + 'name/cos:GetObject', // 读取对象 + 'name/cos:GetObjectAcl', // 获取对象 ACL + 'name/cos:ListBucket', // 列出 Bucket 中的对象 + 'name/cos:ListBucketVersions', // 列出版本(可选) + ], + }), + }; + } + + @route('contestId', Types.ObjectId) + @param('urlB', Types.String) + async post(domainId: string, contestId: ObjectId, urlB: string) { + return { + data: { + domainId, + contestId, + urlB, + }, + }; + } +} + +export async function apply(ctx: Context) { + ctx.Route('monitor-auth', '/monitor/:contestId', CosAuthHandler, PRIV.PRIV_USER_PROFILE); + ctx.Route('monitor-auth', '/monitor/:contestId/admin', CosAuthAdminHandler, PERM.PERM_VIEW_CONTEST); + + global.Hydro.lib.cosStsAuthConfig = { + secretId: await SystemModel.get('iai.secretId'), + secretKey: await SystemModel.get('iai.secretKey'), + proxy: '', + durationSeconds: 1800, // 临时密钥有效期 + bucket: await SystemModel.get('iai.bucket'), + region: await SystemModel.get('iai.region'), + allowPrefix: 'contest/contestId/monitor/user/userId/*', // 允许上传的文件前缀 + // 密钥的权限列表 + allowActions: [ + // 上传操作权限 + 'name/cos:PutObject', + // 'name/cos:PostObject', + // 'name/cos:InitiateMultipartUpload', + // 'name/cos:ListMultipartUploads', + // 'name/cos:ListParts', + // 'name/cos:UploadPart', + // 'name/cos:CompleteMultipartUpload', + ], + }; + global.Hydro.lib.cosContestCheckConfig = { + credential: { + secretId: await SystemModel.get('iai.secretId'), + secretKey: await SystemModel.get('iai.secretKey'), + }, + region: await SystemModel.get('iai.region'), + profile: { + httpProfile: { + endpoint: 'iai.tencentcloudapi.com', + }, + }, + }; +}