feat: build

This commit is contained in:
Pan Qiancheng 2025-10-23 22:25:45 +08:00
parent 0dc7dd4d98
commit 841faface2
23 changed files with 282 additions and 32 deletions

View File

@ -720,6 +720,7 @@ export type AspectDict<ED extends EntityDict> = {
*/
getOAuthClientInfo: (params: {
client_id: string;
currentUserId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<{
data: EntityDict['oauthApplication']['Schema'] | null;
alreadyAuth: boolean;

View File

@ -8,6 +8,7 @@ export declare function loginByOauth<ED extends EntityDict>(params: {
}, context: BRC<ED>): Promise<string>;
export declare function getOAuthClientInfo<ED extends EntityDict>(params: {
client_id: string;
currentUserId?: string;
}, context: BRC<ED>): Promise<{
data: Partial<ED["oauthApplication"]["Schema"]>;
alreadyAuth: boolean;

View File

@ -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(),
}
}, {});

View File

@ -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 });
});
},
},

View File

@ -88,11 +88,113 @@ const oauthTokenEndpoint = {
data: { error: "invalid_grant", error_description: "Authorization code already used", success: false }
};
}
// 如果userIdcode里面的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,

View File

@ -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<UsageAction, UsageState>;
export declare const entityDesc: EntityDesc<Schema, Action, '', {

View File

@ -1,7 +1,9 @@
;
export const UsageActionDef = {
stm: {
revoke: ['granted', "revoked"]
revoke: ['granted', "revoked"],
award: ['unused', 'granted'],
deny: [['unused', 'granted'], 'denied'],
}
};
export const entityDesc = {
@ -19,12 +21,15 @@ export const entityDesc = {
},
action: {
revoke: '撤销授权',
award: '授权',
deny: '拒绝授权',
},
v: {
usageState: {
granted: '已授权',
denied: '未授权',
revoked: '已撤销',
unused: '未使用',
}
}
},
@ -38,6 +43,7 @@ export const entityDesc = {
granted: '#28a745',
denied: '#dc3545',
revoked: '#6c757d',
unused: '#ffc107',
}
}
},

View File

@ -1,7 +1,7 @@
import { ActionDef } from "oak-domain/lib/types/Action";
import { GenericAction } from "oak-domain/lib/actions/action";
export type UsageState = 'granted' | 'denied' | 'revoked' | string;
export type UsageAction = 'revoke' | string;
export type UsageState = 'unused' | 'granted' | 'denied' | 'revoked' | string;
export type UsageAction = 'revoke' | 'award' | 'deny' | string;
export type ParticularAction = UsageAction;
export declare const actions: string[];
export declare const UsageActionDef: ActionDef<UsageAction, UsageState>;

View File

@ -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 = {

View File

@ -25,7 +25,7 @@ export const desc = {
},
usageState: {
type: "enum",
enumeration: ["granted", "denied", "revoked"]
enumeration: ["unused", "granted", "denied", "revoked"]
}
},
actionType: "crud",

View File

@ -7,6 +7,7 @@ export const style = {
granted: '#28a745',
denied: '#dc3545',
revoked: '#6c757d',
unused: '#ffc107',
}
}
};

View File

@ -9,13 +9,16 @@
"usageState": "授权状态"
},
"action": {
"revoke": "撤销授权"
"revoke": "撤销授权",
"award": "授权",
"deny": "拒绝授权"
},
"v": {
"usageState": {
"granted": "已授权",
"denied": "未授权",
"revoked": "已撤销"
"revoked": "已撤销",
"unused": "未使用"
}
}
}

View File

@ -720,6 +720,7 @@ export type AspectDict<ED extends EntityDict> = {
*/
getOAuthClientInfo: (params: {
client_id: string;
currentUserId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<{
data: EntityDict['oauthApplication']['Schema'] | null;
alreadyAuth: boolean;

View File

@ -8,6 +8,7 @@ export declare function loginByOauth<ED extends EntityDict>(params: {
}, context: BRC<ED>): Promise<string>;
export declare function getOAuthClientInfo<ED extends EntityDict>(params: {
client_id: string;
currentUserId?: string;
}, context: BRC<ED>): Promise<{
data: Partial<ED["oauthApplication"]["Schema"]>;
alreadyAuth: boolean;

View File

@ -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(),
}
}, {});

View File

@ -91,11 +91,113 @@ const oauthTokenEndpoint = {
data: { error: "invalid_grant", error_description: "Authorization code already used", success: false }
};
}
// 如果userIdcode里面的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,

View File

@ -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<UsageAction, UsageState>;
export declare const entityDesc: EntityDesc<Schema, Action, '', {

View File

@ -4,7 +4,9 @@ exports.entityDesc = exports.UsageActionDef = void 0;
;
exports.UsageActionDef = {
stm: {
revoke: ['granted', "revoked"]
revoke: ['granted', "revoked"],
award: ['unused', 'granted'],
deny: [['unused', 'granted'], 'denied'],
}
};
exports.entityDesc = {
@ -22,12 +24,15 @@ exports.entityDesc = {
},
action: {
revoke: '撤销授权',
award: '授权',
deny: '拒绝授权',
},
v: {
usageState: {
granted: '已授权',
denied: '未授权',
revoked: '已撤销',
unused: '未使用',
}
}
},
@ -41,6 +46,7 @@ exports.entityDesc = {
granted: '#28a745',
denied: '#dc3545',
revoked: '#6c757d',
unused: '#ffc107',
}
}
},

View File

@ -1,7 +1,7 @@
import { ActionDef } from "oak-domain/lib/types/Action";
import { GenericAction } from "oak-domain/lib/actions/action";
export type UsageState = 'granted' | 'denied' | 'revoked' | string;
export type UsageAction = 'revoke' | string;
export type UsageState = 'unused' | 'granted' | 'denied' | 'revoked' | string;
export type UsageAction = 'revoke' | 'award' | 'deny' | string;
export type ParticularAction = UsageAction;
export declare const actions: string[];
export declare const UsageActionDef: ActionDef<UsageAction, UsageState>;

View File

@ -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 = {

View File

@ -28,7 +28,7 @@ exports.desc = {
},
usageState: {
type: "enum",
enumeration: ["granted", "denied", "revoked"]
enumeration: ["unused", "granted", "denied", "revoked"]
}
},
actionType: "crud",

View File

@ -10,6 +10,7 @@ exports.style = {
granted: '#28a745',
denied: '#dc3545',
revoked: '#6c757d',
unused: '#ffc107',
}
}
};

View File

@ -9,13 +9,16 @@
"usageState": "授权状态"
},
"action": {
"revoke": "撤销授权"
"revoke": "撤销授权",
"award": "授权",
"deny": "拒绝授权"
},
"v": {
"usageState": {
"granted": "已授权",
"denied": "未授权",
"revoked": "已撤销"
"revoked": "已撤销",
"unused": "未使用"
}
}
}