diff --git a/es/aspects/AspectDict.d.ts b/es/aspects/AspectDict.d.ts index 7772a7202..4bff9b455 100644 --- a/es/aspects/AspectDict.d.ts +++ b/es/aspects/AspectDict.d.ts @@ -720,6 +720,7 @@ export type AspectDict = { */ getOAuthClientInfo: (params: { client_id: string; + currentUserId?: string; }, context: BackendRuntimeContext) => Promise<{ data: EntityDict['oauthApplication']['Schema'] | null; alreadyAuth: boolean; diff --git a/es/aspects/oauth.d.ts b/es/aspects/oauth.d.ts index bb65bfe92..670986cc3 100644 --- a/es/aspects/oauth.d.ts +++ b/es/aspects/oauth.d.ts @@ -8,6 +8,7 @@ export declare function loginByOauth(params: { }, context: BRC): Promise; export declare function getOAuthClientInfo(params: { client_id: string; + currentUserId?: string; }, context: BRC): Promise<{ data: Partial; alreadyAuth: boolean; diff --git a/es/aspects/oauth.js b/es/aspects/oauth.js index bb7be9ace..dc5c457f3 100644 --- a/es/aspects/oauth.js +++ b/es/aspects/oauth.js @@ -181,7 +181,7 @@ export async function loginByOauth(params, context) { } } export async function getOAuthClientInfo(params, context) { - const { client_id } = params; + const { client_id, currentUserId } = params; const closeRootMode = context.openRootMode(); const systemId = context.getSystemId(); const applicationId = context.getApplicationId(); @@ -219,14 +219,20 @@ export async function getOAuthClientInfo(params, context) { $exists: false } }, + usageState: 'granted', code: { + // 当前应用下的认证客户端 oauthApp: { id: client_id }, - applicationId: applicationId + applicationId: applicationId, + userId: currentUserId, } } }, {}); + if (hasAuth) { + console.log("用户已有授权记录:", currentUserId, hasAuth.id, hasAuth.userId, hasAuth.applicationId, hasAuth.usageState); + } if (!oauthApp) { throw new OakUserException('未经授权的客户端应用'); } @@ -287,7 +293,7 @@ export async function authorize(params, context) { id: recordId, userId: context.getCurrentUserId(), applicationId: oauthApp.id, - usageState: action === 'grant' ? 'granted' : 'denied', + usageState: action === 'grant' ? 'unused' : 'denied', authorizedAt: Date.now(), } }, {}); diff --git a/es/components/login/oauth/authorize/index.js b/es/components/login/oauth/authorize/index.js index 55fdadffd..e0e80ce0d 100644 --- a/es/components/login/oauth/authorize/index.js +++ b/es/components/login/oauth/authorize/index.js @@ -87,8 +87,10 @@ export default OakComponent({ } this.features.cache.exec("getOAuthClientInfo", { client_id: clientId, + currentUserId: userId, }).then((clientInfo) => { if (!clientInfo.result) { + this.setState({ loading: false }); this.setState({ hasError: true, errorMsg: 'oauth.authorize.error.invalid_client_id', @@ -102,15 +104,17 @@ export default OakComponent({ // 已经授权过,直接跳转 this.handleGrant(); } + else { + this.setState({ loading: false }); + } } }).catch((err) => { + this.setState({ loading: false }); console.error('Error loading OAuth client info:', err); this.setState({ hasError: true, errorMsg: err.message || 'oauth.authorize.error.unknown', }); - }).finally(() => { - this.setState({ loading: false }); }); }, }, diff --git a/es/endpoints/oauth.js b/es/endpoints/oauth.js index e5c71beff..99a2cd0b5 100644 --- a/es/endpoints/oauth.js +++ b/es/endpoints/oauth.js @@ -88,11 +88,113 @@ const oauthTokenEndpoint = { data: { error: "invalid_grant", error_description: "Authorization code already used", success: false } }; } + // 如果userId,code里面的appId都一样,则说明是这个用户在这个应用下第二次授权的,直接返回 + const [existingToken] = await context.select("oauthToken", { + data: { + id: 1, + accessToken: 1, + refreshToken: 1, + accessExpiresAt: 1, + refreshExpiresAt: 1, + }, + filter: { + userId: authCodeRecord.userId, + code: { + oauthAppId: app.id, + }, + revokedAt: { + $exists: false, + } + } + }, {}); + // 如果存在且未过期,直接返回 + if (existingToken && (existingToken.accessExpiresAt > Date.now())) { + console.log("Existing valid token found, returning it directly"); + // 刷新最后一次使用 + await context.operate("oauthToken", { + id: await generateNewIdAsync(), + action: "update", + data: { + lastUsedAt: Date.now(), + }, + filter: { + id: existingToken.id, + } + }, {}); + // // 创建记录 + // await context.operate("oauthUserAuthorization", { + // id: await generateNewIdAsync(), + // action: "update", + // data: { + // tokenId: existingToken.id, + // usageState: 'granted', + // }, + // filter: { + // codeId: authCodeRecord.id, + // } + // }, {}) + await context.commit(); + return { + statusCode: 200, + data: { + access_token: existingToken.accessToken, + token_type: "Bearer", + expires_in: existingToken.accessExpiresAt - Date.now(), + refresh_token: existingToken.refreshToken, + refresh_expires_in: existingToken.refreshExpiresAt - Date.now(), + success: true, + } + }; + } + const expiresIn = 3600; // 1 hour + const refreshTokenExpiresIn = 86400 * 30; // 30 days + // 如果过期就顺带刷新了 + if (existingToken && (existingToken.refreshExpiresAt < Date.now())) { + console.log("Existing token expired, refreshing it"); + const newAccessToken = randomUUID(); + const newRefreshToken = randomUUID(); + await context.operate("oauthToken", { + id: await generateNewIdAsync(), + action: "update", + data: { + lastUsedAt: Date.now(), + accessToken: newAccessToken, + refreshToken: newRefreshToken, + accessExpiresAt: Date.now() + expiresIn * 1000, + refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, + } + }, {}); + // // 创建记录 + // await context.operate("oauthUserAuthorization", { + // id: await generateNewIdAsync(), + // action: "update", + // data: { + // tokenId: existingToken.id, + // usageState: 'granted', + // }, + // filter: { + // codeId: authCodeRecord.id, + // } + // }, {}) + await context.commit(); + return { + statusCode: 200, + data: { + access_token: newAccessToken, + token_type: "Bearer", + expires_in: expiresIn, + refresh_token: newRefreshToken, + refresh_expires_in: refreshTokenExpiresIn, + success: true, + } + }; + } + // 有一种情况是access和refresh都过期了,但是用户又重新用code来换token + // 这种情况下不能刷新老的,也要当作新的处理 // 创建accessToken const genaccessToken = randomUUID(); - const expiresIn = 3600; // 1 hour const refreshToken = randomUUID(); - const refreshTokenExpiresIn = 86400 * 30; // 30 days + console.log("Creating new access token and refresh token"); // create const tokenId = await generateNewIdAsync(); await context.operate("oauthToken", { @@ -114,6 +216,7 @@ const oauthTokenEndpoint = { action: "update", data: { tokenId: tokenId, + usageState: 'granted', }, filter: { codeId: authCodeRecord.id, diff --git a/es/entities/OauthUserAuthorization.d.ts b/es/entities/OauthUserAuthorization.d.ts index 626f50cc2..25fcb0542 100644 --- a/es/entities/OauthUserAuthorization.d.ts +++ b/es/entities/OauthUserAuthorization.d.ts @@ -13,8 +13,8 @@ export interface Schema extends EntityShape { code?: OauthAuthorizationCode; token?: OauthToken; } -export type UsageState = 'granted' | 'denied' | 'revoked'; -export type UsageAction = 'revoke'; +export type UsageState = 'unused' | 'granted' | 'denied' | 'revoked'; +export type UsageAction = 'revoke' | 'award' | 'deny'; export type Action = UsageAction; export declare const UsageActionDef: ActionDef; export declare const entityDesc: EntityDesc; diff --git a/es/oak-app-domain/OauthUserAuthorization/Action.js b/es/oak-app-domain/OauthUserAuthorization/Action.js index feb1ba707..7284075a3 100644 --- a/es/oak-app-domain/OauthUserAuthorization/Action.js +++ b/es/oak-app-domain/OauthUserAuthorization/Action.js @@ -1,7 +1,9 @@ -export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "revoke"]; +export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "revoke", "award", "deny"]; export const UsageActionDef = { stm: { - revoke: ['granted', "revoked"] + revoke: ['granted', "revoked"], + award: ['unused', 'granted'], + deny: [['unused', 'granted'], 'denied'], } }; export const actionDefDict = { diff --git a/es/oak-app-domain/OauthUserAuthorization/Storage.js b/es/oak-app-domain/OauthUserAuthorization/Storage.js index 88df04791..4867d0327 100644 --- a/es/oak-app-domain/OauthUserAuthorization/Storage.js +++ b/es/oak-app-domain/OauthUserAuthorization/Storage.js @@ -25,7 +25,7 @@ export const desc = { }, usageState: { type: "enum", - enumeration: ["granted", "denied", "revoked"] + enumeration: ["unused", "granted", "denied", "revoked"] } }, actionType: "crud", diff --git a/es/oak-app-domain/OauthUserAuthorization/Style.js b/es/oak-app-domain/OauthUserAuthorization/Style.js index 79cf7e378..4d17b02a6 100644 --- a/es/oak-app-domain/OauthUserAuthorization/Style.js +++ b/es/oak-app-domain/OauthUserAuthorization/Style.js @@ -7,6 +7,7 @@ export const style = { granted: '#28a745', denied: '#dc3545', revoked: '#6c757d', + unused: '#ffc107', } } }; diff --git a/es/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json b/es/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json index 4722d0b2c..4a64dbbde 100644 --- a/es/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json +++ b/es/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json @@ -9,13 +9,16 @@ "usageState": "授权状态" }, "action": { - "revoke": "撤销授权" + "revoke": "撤销授权", + "award": "授权", + "deny": "拒绝授权" }, "v": { "usageState": { "granted": "已授权", "denied": "未授权", - "revoked": "已撤销" + "revoked": "已撤销", + "unused": "未使用" } } } diff --git a/lib/aspects/AspectDict.d.ts b/lib/aspects/AspectDict.d.ts index 7772a7202..4bff9b455 100644 --- a/lib/aspects/AspectDict.d.ts +++ b/lib/aspects/AspectDict.d.ts @@ -720,6 +720,7 @@ export type AspectDict = { */ getOAuthClientInfo: (params: { client_id: string; + currentUserId?: string; }, context: BackendRuntimeContext) => Promise<{ data: EntityDict['oauthApplication']['Schema'] | null; alreadyAuth: boolean; diff --git a/lib/aspects/oauth.d.ts b/lib/aspects/oauth.d.ts index bb65bfe92..670986cc3 100644 --- a/lib/aspects/oauth.d.ts +++ b/lib/aspects/oauth.d.ts @@ -8,6 +8,7 @@ export declare function loginByOauth(params: { }, context: BRC): Promise; export declare function getOAuthClientInfo(params: { client_id: string; + currentUserId?: string; }, context: BRC): Promise<{ data: Partial; alreadyAuth: boolean; diff --git a/lib/aspects/oauth.js b/lib/aspects/oauth.js index 51ffc3056..4f8098860 100644 --- a/lib/aspects/oauth.js +++ b/lib/aspects/oauth.js @@ -188,7 +188,7 @@ async function loginByOauth(params, context) { } } async function getOAuthClientInfo(params, context) { - const { client_id } = params; + const { client_id, currentUserId } = params; const closeRootMode = context.openRootMode(); const systemId = context.getSystemId(); const applicationId = context.getApplicationId(); @@ -226,14 +226,20 @@ async function getOAuthClientInfo(params, context) { $exists: false } }, + usageState: 'granted', code: { + // 当前应用下的认证客户端 oauthApp: { id: client_id }, - applicationId: applicationId + applicationId: applicationId, + userId: currentUserId, } } }, {}); + if (hasAuth) { + console.log("用户已有授权记录:", currentUserId, hasAuth.id, hasAuth.userId, hasAuth.applicationId, hasAuth.usageState); + } if (!oauthApp) { throw new types_1.OakUserException('未经授权的客户端应用'); } @@ -294,7 +300,7 @@ async function authorize(params, context) { id: recordId, userId: context.getCurrentUserId(), applicationId: oauthApp.id, - usageState: action === 'grant' ? 'granted' : 'denied', + usageState: action === 'grant' ? 'unused' : 'denied', authorizedAt: Date.now(), } }, {}); diff --git a/lib/endpoints/oauth.js b/lib/endpoints/oauth.js index d16521262..5294b7d8b 100644 --- a/lib/endpoints/oauth.js +++ b/lib/endpoints/oauth.js @@ -91,11 +91,113 @@ const oauthTokenEndpoint = { data: { error: "invalid_grant", error_description: "Authorization code already used", success: false } }; } + // 如果userId,code里面的appId都一样,则说明是这个用户在这个应用下第二次授权的,直接返回 + const [existingToken] = await context.select("oauthToken", { + data: { + id: 1, + accessToken: 1, + refreshToken: 1, + accessExpiresAt: 1, + refreshExpiresAt: 1, + }, + filter: { + userId: authCodeRecord.userId, + code: { + oauthAppId: app.id, + }, + revokedAt: { + $exists: false, + } + } + }, {}); + // 如果存在且未过期,直接返回 + if (existingToken && (existingToken.accessExpiresAt > Date.now())) { + console.log("Existing valid token found, returning it directly"); + // 刷新最后一次使用 + await context.operate("oauthToken", { + id: await (0, uuid_1.generateNewIdAsync)(), + action: "update", + data: { + lastUsedAt: Date.now(), + }, + filter: { + id: existingToken.id, + } + }, {}); + // // 创建记录 + // await context.operate("oauthUserAuthorization", { + // id: await generateNewIdAsync(), + // action: "update", + // data: { + // tokenId: existingToken.id, + // usageState: 'granted', + // }, + // filter: { + // codeId: authCodeRecord.id, + // } + // }, {}) + await context.commit(); + return { + statusCode: 200, + data: { + access_token: existingToken.accessToken, + token_type: "Bearer", + expires_in: existingToken.accessExpiresAt - Date.now(), + refresh_token: existingToken.refreshToken, + refresh_expires_in: existingToken.refreshExpiresAt - Date.now(), + success: true, + } + }; + } + const expiresIn = 3600; // 1 hour + const refreshTokenExpiresIn = 86400 * 30; // 30 days + // 如果过期就顺带刷新了 + if (existingToken && (existingToken.refreshExpiresAt < Date.now())) { + console.log("Existing token expired, refreshing it"); + const newAccessToken = (0, crypto_1.randomUUID)(); + const newRefreshToken = (0, crypto_1.randomUUID)(); + await context.operate("oauthToken", { + id: await (0, uuid_1.generateNewIdAsync)(), + action: "update", + data: { + lastUsedAt: Date.now(), + accessToken: newAccessToken, + refreshToken: newRefreshToken, + accessExpiresAt: Date.now() + expiresIn * 1000, + refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, + } + }, {}); + // // 创建记录 + // await context.operate("oauthUserAuthorization", { + // id: await generateNewIdAsync(), + // action: "update", + // data: { + // tokenId: existingToken.id, + // usageState: 'granted', + // }, + // filter: { + // codeId: authCodeRecord.id, + // } + // }, {}) + await context.commit(); + return { + statusCode: 200, + data: { + access_token: newAccessToken, + token_type: "Bearer", + expires_in: expiresIn, + refresh_token: newRefreshToken, + refresh_expires_in: refreshTokenExpiresIn, + success: true, + } + }; + } + // 有一种情况是access和refresh都过期了,但是用户又重新用code来换token + // 这种情况下不能刷新老的,也要当作新的处理 // 创建accessToken const genaccessToken = (0, crypto_1.randomUUID)(); - const expiresIn = 3600; // 1 hour const refreshToken = (0, crypto_1.randomUUID)(); - const refreshTokenExpiresIn = 86400 * 30; // 30 days + console.log("Creating new access token and refresh token"); // create const tokenId = await (0, uuid_1.generateNewIdAsync)(); await context.operate("oauthToken", { @@ -117,6 +219,7 @@ const oauthTokenEndpoint = { action: "update", data: { tokenId: tokenId, + usageState: 'granted', }, filter: { codeId: authCodeRecord.id, diff --git a/lib/entities/OauthUserAuthorization.d.ts b/lib/entities/OauthUserAuthorization.d.ts index 626f50cc2..25fcb0542 100644 --- a/lib/entities/OauthUserAuthorization.d.ts +++ b/lib/entities/OauthUserAuthorization.d.ts @@ -13,8 +13,8 @@ export interface Schema extends EntityShape { code?: OauthAuthorizationCode; token?: OauthToken; } -export type UsageState = 'granted' | 'denied' | 'revoked'; -export type UsageAction = 'revoke'; +export type UsageState = 'unused' | 'granted' | 'denied' | 'revoked'; +export type UsageAction = 'revoke' | 'award' | 'deny'; export type Action = UsageAction; export declare const UsageActionDef: ActionDef; export declare const entityDesc: EntityDesc; diff --git a/lib/oak-app-domain/OauthUserAuthorization/Action.js b/lib/oak-app-domain/OauthUserAuthorization/Action.js index 9ff1ad1ae..8b7797dad 100644 --- a/lib/oak-app-domain/OauthUserAuthorization/Action.js +++ b/lib/oak-app-domain/OauthUserAuthorization/Action.js @@ -1,10 +1,12 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.actionDefDict = exports.UsageActionDef = exports.actions = void 0; -exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "revoke"]; +exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "revoke", "award", "deny"]; exports.UsageActionDef = { stm: { - revoke: ['granted', "revoked"] + revoke: ['granted', "revoked"], + award: ['unused', 'granted'], + deny: [['unused', 'granted'], 'denied'], } }; exports.actionDefDict = { diff --git a/lib/oak-app-domain/OauthUserAuthorization/Storage.js b/lib/oak-app-domain/OauthUserAuthorization/Storage.js index 1761387c3..d23e3bdd3 100644 --- a/lib/oak-app-domain/OauthUserAuthorization/Storage.js +++ b/lib/oak-app-domain/OauthUserAuthorization/Storage.js @@ -28,7 +28,7 @@ exports.desc = { }, usageState: { type: "enum", - enumeration: ["granted", "denied", "revoked"] + enumeration: ["unused", "granted", "denied", "revoked"] } }, actionType: "crud", diff --git a/lib/oak-app-domain/OauthUserAuthorization/Style.js b/lib/oak-app-domain/OauthUserAuthorization/Style.js index b8e5c9d55..7e0f9e787 100644 --- a/lib/oak-app-domain/OauthUserAuthorization/Style.js +++ b/lib/oak-app-domain/OauthUserAuthorization/Style.js @@ -10,6 +10,7 @@ exports.style = { granted: '#28a745', denied: '#dc3545', revoked: '#6c757d', + unused: '#ffc107', } } }; diff --git a/lib/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json b/lib/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json index 4722d0b2c..4a64dbbde 100644 --- a/lib/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json +++ b/lib/oak-app-domain/OauthUserAuthorization/locales/zh_CN.json @@ -9,13 +9,16 @@ "usageState": "授权状态" }, "action": { - "revoke": "撤销授权" + "revoke": "撤销授权", + "award": "授权", + "deny": "拒绝授权" }, "v": { "usageState": { "granted": "已授权", "denied": "未授权", - "revoked": "已撤销" + "revoked": "已撤销", + "unused": "未使用" } } }