fix: 修复oauth-endpoint相关问题,并允许重复注册处理器

This commit is contained in:
Pan Qiancheng 2025-12-24 15:13:16 +08:00
parent ba434da707
commit a3ec5fc808
7 changed files with 1367 additions and 1250 deletions

View File

@ -26,271 +26,284 @@ const oauthTokenEndpoint = {
type: "free", type: "free",
fn: async (contextBuilder, params, header, req, body) => { fn: async (contextBuilder, params, header, req, body) => {
const context = await contextBuilder(); const context = await contextBuilder();
const { client_id, client_secret, grant_type, code, redirect_uri, code_verifier // PKCE支持 try {
} = body; const { client_id, client_secret, grant_type, code, redirect_uri, code_verifier // PKCE支持
const [app] = await context.select("oauthApplication", { } = body;
data: { const [app] = await context.select("oauthApplication", {
id: 1,
clientSecret: 1,
},
filter: {
id: client_id,
}
}, {});
if (!app) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_client", error_description: "Client not found", success: false }
};
}
if (app.clientSecret !== client_secret) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_client", error_description: "Client secret mismatch", success: false }
};
}
// grant_type几种类型 目前只支持authorization_code
if (grant_type !== "authorization_code") {
await context.commit();
return {
statusCode: 400,
data: { error: "unsupported_grant_type", error_description: "Only authorization_code grant type is supported", success: false }
};
}
// 找code的记录
const [authCodeRecord] = await context.select("oauthAuthorizationCode", {
data: {
id: 1,
code: 1,
redirectUri: 1,
userId: 1,
expiresAt: 1,
usedAt: 1,
codeChallenge: 1,
codeChallengeMethod: 1,
},
filter: {
code,
}
}, {});
// 找不到记录
if (!authCodeRecord) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Invalid authorization code", success: false }
};
}
// PKCE 验证
if (authCodeRecord.codeChallenge) {
if (!code_verifier) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_request",
error_description: "code_verifier is required",
success: false
}
};
}
// 验证 code_verifier
const isValid = await verifyPKCE(code_verifier, authCodeRecord.codeChallenge, authCodeRecord.codeChallengeMethod);
if (!isValid) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_grant",
error_description: "Invalid code_verifier",
success: false
}
};
}
}
// 验证redirect_uri
if (authCodeRecord.redirectUri !== redirect_uri) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Redirect URI mismatch", success: false }
};
}
// 验证过期
if (authCodeRecord.expiresAt < Date.now()) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Authorization code expired", success: false }
};
}
// 验证是否已使用
if (authCodeRecord.usedAt) {
await context.commit();
return {
statusCode: 400,
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: { data: {
lastUsedAt: Date.now(), id: 1,
clientSecret: 1,
}, },
filter: { filter: {
id: existingToken.id, id: client_id,
} }
}, {}); }, {});
// // 创建记录 if (!app) {
// await context.operate("oauthUserAuthorization", { await context.commit();
// id: await generateNewIdAsync(), return {
// action: "update", statusCode: 400,
// data: { data: { error: "invalid_client", error_description: "Client not found", success: false }
// tokenId: existingToken.id, };
// usageState: 'granted', }
// }, if (app.clientSecret !== client_secret) {
// filter: { await context.commit();
// codeId: authCodeRecord.id, return {
// } statusCode: 400,
// }, {}) data: { error: "invalid_client", error_description: "Client secret mismatch", success: false }
await context.commit(); };
return { }
statusCode: 200, // grant_type几种类型 目前只支持authorization_code
if (grant_type !== "authorization_code") {
await context.commit();
return {
statusCode: 400,
data: { error: "unsupported_grant_type", error_description: "Only authorization_code grant type is supported", success: false }
};
}
// 找code的记录
const [authCodeRecord] = await context.select("oauthAuthorizationCode", {
data: { data: {
access_token: existingToken.accessToken, id: 1,
token_type: "Bearer", code: 1,
expires_in: existingToken.accessExpiresAt - Date.now(), redirectUri: 1,
refresh_token: existingToken.refreshToken, userId: 1,
refresh_expires_in: existingToken.refreshExpiresAt - Date.now(), expiresAt: 1,
success: true, usedAt: 1,
codeChallenge: 1,
codeChallengeMethod: 1,
},
filter: {
code,
} }
}; }, {});
} // 找不到记录
const expiresIn = 3600; // 1 hour if (!authCodeRecord) {
const refreshTokenExpiresIn = 86400 * 30; // 30 days await context.commit();
// 如果过期就顺带刷新了(refresh token没过期 accessToken过期了) return {
if (existingToken && (existingToken.refreshExpiresAt > Date.now())) { statusCode: 400,
console.log("Existing token expired, refreshing it"); data: { error: "invalid_grant", error_description: "Invalid authorization code", success: false }
const newAccessToken = randomUUID(); };
const newRefreshToken = randomUUID(); }
// PKCE 验证
if (authCodeRecord.codeChallenge) {
if (!code_verifier) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_request",
error_description: "code_verifier is required",
success: false
}
};
}
// 验证 code_verifier
const isValid = await verifyPKCE(code_verifier, authCodeRecord.codeChallenge, authCodeRecord.codeChallengeMethod);
if (!isValid) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_grant",
error_description: "Invalid code_verifier",
success: false
}
};
}
}
// 验证redirect_uri
if (authCodeRecord.redirectUri !== redirect_uri) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Redirect URI mismatch", success: false }
};
}
// 验证过期
if (authCodeRecord.expiresAt < Date.now()) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Authorization code expired", success: false }
};
}
// 验证是否已使用
if (authCodeRecord.usedAt) {
await context.commit();
return {
statusCode: 400,
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
// 如果过期就顺带刷新了(refresh token没过期 accessToken过期了)
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,
},
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: 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 refreshToken = randomUUID();
console.log("Creating new access token and refresh token");
// create
const tokenId = await generateNewIdAsync();
await context.operate("oauthToken", { await context.operate("oauthToken", {
id: await generateNewIdAsync(),
action: "create",
data: {
id: tokenId,
accessToken: genaccessToken,
refreshToken: refreshToken,
userId: authCodeRecord.userId,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
codeId: authCodeRecord.id,
}
}, {});
// 创建记录
await context.operate("oauthUserAuthorization", {
id: await generateNewIdAsync(), id: await generateNewIdAsync(),
action: "update", action: "update",
data: { data: {
lastUsedAt: Date.now(), tokenId: tokenId,
accessToken: newAccessToken, usageState: 'granted',
refreshToken: newRefreshToken, },
accessExpiresAt: Date.now() + expiresIn * 1000, filter: {
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, codeId: authCodeRecord.id,
}
}, {});
// 标记code为已使用
await context.operate("oauthAuthorizationCode", {
id: await generateNewIdAsync(),
action: "update",
data: {
usedAt: Date.now(),
},
filter: {
id: authCodeRecord.id,
} }
}, {}); }, {});
// // 创建记录
// await context.operate("oauthUserAuthorization", {
// id: await generateNewIdAsync(),
// action: "update",
// data: {
// tokenId: existingToken.id,
// usageState: 'granted',
// },
// filter: {
// codeId: authCodeRecord.id,
// }
// }, {})
await context.commit(); await context.commit();
return { return {
statusCode: 200, statusCode: 200,
data: { data: {
access_token: newAccessToken, access_token: genaccessToken,
token_type: "Bearer", token_type: "Bearer",
expires_in: expiresIn, expires_in: expiresIn,
refresh_token: newRefreshToken, refresh_token: refreshToken,
refresh_expires_in: refreshTokenExpiresIn, refresh_expires_in: refreshTokenExpiresIn,
success: true, success: true,
} }
}; };
} }
// 有一种情况是access和refresh都过期了但是用户又重新用code来换token catch (err) {
// 这种情况下不能刷新老的,也要当作新的处理 console.error("Error in oauth token endpoint:", err);
// 创建accessToken await context.rollback();
const genaccessToken = randomUUID(); return {
const refreshToken = randomUUID(); statusCode: 500,
console.log("Creating new access token and refresh token"); data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
// create };
const tokenId = await generateNewIdAsync(); }
await context.operate("oauthToken", {
id: await generateNewIdAsync(),
action: "create",
data: {
id: tokenId,
accessToken: genaccessToken,
refreshToken: refreshToken,
userId: authCodeRecord.userId,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
codeId: authCodeRecord.id,
}
}, {});
// 创建记录
await context.operate("oauthUserAuthorization", {
id: await generateNewIdAsync(),
action: "update",
data: {
tokenId: tokenId,
usageState: 'granted',
},
filter: {
codeId: authCodeRecord.id,
}
}, {});
// 标记code为已使用
await context.operate("oauthAuthorizationCode", {
id: await generateNewIdAsync(),
action: "update",
data: {
usedAt: Date.now(),
},
filter: {
id: authCodeRecord.id,
}
}, {});
await context.commit();
return {
statusCode: 200,
data: {
access_token: genaccessToken,
token_type: "Bearer",
expires_in: expiresIn,
refresh_token: refreshToken,
refresh_expires_in: refreshTokenExpiresIn,
success: true,
}
};
} }
}; };
const oauthUserInfoEndpoint = { const oauthUserInfoEndpoint = {
@ -301,37 +314,47 @@ const oauthUserInfoEndpoint = {
fn: async (contextBuilder, params, header, req, body) => { fn: async (contextBuilder, params, header, req, body) => {
const context = await contextBuilder(); const context = await contextBuilder();
const token = header.authorization; // Bearer token const token = header.authorization; // Bearer token
const checkResult = await checkOauthTokenAvaliable(context, token); try {
if (checkResult.error) { const checkResult = await checkOauthTokenAvaliable(context, token);
if (checkResult.error) {
await context.commit();
return {
statusCode: checkResult.statusCode || 401,
data: { error: checkResult.error, success: false }
};
}
const tokenRecord = checkResult.tokenRecord;
assert(tokenRecord?.user, "User must be present in token record");
assert(tokenRecord?.code?.application, "Application must be present in token record");
const extrafile = tokenRecord.user.extraFile$entity?.[0];
const application = tokenRecord.code.application;
let avatarUrl = '';
if (extrafile) {
avatarUrl = await composeFileUrlBackend(application, extrafile, context);
}
await context.commit(); await context.commit();
return { return {
statusCode: checkResult.statusCode || 401, statusCode: 200, data: {
data: { error: checkResult.error, success: false } userInfo: {
id: tokenRecord.user.id,
name: tokenRecord.user.name,
nickname: tokenRecord.user.nickname,
birth: tokenRecord.user.birth,
gender: tokenRecord.user.gender,
avatarUrl: avatarUrl,
},
error: null
}
}; };
} }
const tokenRecord = checkResult.tokenRecord; catch (err) {
assert(tokenRecord?.user, "User must be present in token record"); console.error("Error in oauth userinfo endpoint:", err);
assert(tokenRecord?.code?.application, "Application must be present in token record"); await context.rollback();
const extrafile = tokenRecord.user.extraFile$entity?.[0]; return {
const application = tokenRecord.code.application; statusCode: 500,
let avatarUrl = ''; data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
if (extrafile) { };
avatarUrl = await composeFileUrlBackend(application, extrafile, context);
} }
await context.commit();
return {
statusCode: 200, data: {
userInfo: {
id: tokenRecord.user.id,
name: tokenRecord.user.name,
nickname: tokenRecord.user.nickname,
birth: tokenRecord.user.birth,
gender: tokenRecord.user.gender,
avatarUrl: avatarUrl,
},
error: null
}
};
} }
}; };
const refreshTokenEndpoint = { const refreshTokenEndpoint = {
@ -366,88 +389,98 @@ const refreshTokenEndpoint = {
}; };
} }
const context = await contextBuilder(); const context = await contextBuilder();
const [oauthApp] = await context.select("oauthApplication", { try {
data: { const [oauthApp] = await context.select("oauthApplication", {
id: 1, data: {
}, id: 1,
filter: { },
clientSecret: client_secret, filter: {
id: client_id, clientSecret: client_secret,
id: client_id,
}
}, {});
if (!oauthApp) {
await context.commit();
return {
statusCode: 401,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false }
};
} }
}, {}); const [tokenRecord] = await context.select("oauthToken", {
if (!oauthApp) { data: {
await context.commit(); id: 1,
return { userId: 1,
statusCode: 401, accessToken: 1,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false } accessExpiresAt: 1,
}; refreshToken: 1,
} refreshExpiresAt: 1,
const [tokenRecord] = await context.select("oauthToken", { code: {
data: { applicationId: 1,
id: 1, oauthApp: {
userId: 1, id: 1,
accessToken: 1, }
accessExpiresAt: 1, }
refreshToken: 1, },
refreshExpiresAt: 1, filter: {
code: { refreshToken: refresh_token,
applicationId: 1, code: {
oauthApp: { oauthAppId: oauthApp.id,
id: 1,
} }
} }
}, }, {});
filter: { if (!tokenRecord) {
refreshToken: refresh_token, await context.commit();
code: { return {
oauthAppId: oauthApp.id, statusCode: 400,
data: { error: "invalid_grant", error_description: "Invalid refresh token", success: false }
};
}
if (tokenRecord.refreshExpiresAt < Date.now()) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Refresh token expired", success: false }
};
}
// 生成新的令牌
const newAccessToken = randomUUID();
const newRefreshToken = randomUUID();
const expiresIn = 3600; // 1 hour
const refreshTokenExpiresIn = 86400 * 30; // 30 days
await context.operate("oauthToken", {
id: await generateNewIdAsync(),
action: "update",
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
},
filter: {
id: tokenRecord.id,
} }
} }, {});
}, {});
if (!tokenRecord) {
await context.commit(); await context.commit();
return { return {
statusCode: 400, statusCode: 200,
data: { error: "invalid_grant", error_description: "Invalid refresh token", success: false } data: {
access_token: newAccessToken,
token_type: "Bearer",
expires_in: expiresIn,
refresh_token: newRefreshToken,
refresh_expires_in: refreshTokenExpiresIn,
success: true,
}
}; };
} }
if (tokenRecord.refreshExpiresAt < Date.now()) { catch (err) {
await context.commit(); console.error("Error in refresh token endpoint:", err);
await context.rollback();
return { return {
statusCode: 400, statusCode: 500,
data: { error: "invalid_grant", error_description: "Refresh token expired", success: false } data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
}; };
} }
// 生成新的令牌
const newAccessToken = randomUUID();
const newRefreshToken = randomUUID();
const expiresIn = 3600; // 1 hour
const refreshTokenExpiresIn = 86400 * 30; // 30 days
await context.operate("oauthToken", {
id: await generateNewIdAsync(),
action: "update",
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
},
filter: {
id: tokenRecord.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,
}
};
} }
}; };
const oauthRevocationEndpoint = { const oauthRevocationEndpoint = {
@ -475,64 +508,74 @@ const oauthRevocationEndpoint = {
}; };
} }
const context = await contextBuilder(); const context = await contextBuilder();
const [oauthApp] = await context.select("oauthApplication", { try {
data: { id: 1 }, const [oauthApp] = await context.select("oauthApplication", {
filter: { clientSecret: client_secret, id: client_id } data: { id: 1 },
}, {}); filter: { clientSecret: client_secret, id: client_id }
if (!oauthApp) { }, {});
if (!oauthApp) {
await context.commit();
return {
statusCode: 401,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false }
};
}
// 3. 查找令牌记录
let tokenRecord = null;
const tokenProjection = {
data: { id: 1, code: { oauthAppId: 1 } },
filter: {}
};
// 尝试查找 Refresh Token
if (!token_type_hint || token_type_hint === 'refresh_token') {
tokenProjection.filter = { refreshToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
}
// 如果没找到,且 hint 不是 'refresh_token',则尝试查找 Access Token
if (!tokenRecord && (!token_type_hint || token_type_hint === 'access_token')) {
tokenProjection.filter = { accessToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
}
// 4. 撤销操作(无论找到与否,都返回 200但如果找到则执行失效操作
if (tokenRecord) {
// const pastTime = Date.now() - 1000;
// // 将 Access Token 和 Refresh Token 的过期时间都设为过去,使其立即失效
// await context.operate("oauthToken", {
// id: await generateNewIdAsync(),
// action: "update",
// data: {
// accessExpiresAt: pastTime,
// refreshExpiresAt: pastTime,
// },
// filter: {
// id: tokenRecord.id,
// }
// }, {});
// 使用这个token的认证记录都撤销掉在trigger里会自动设置 revokedAt
await context.operate("oauthUserAuthorization", {
id: await generateNewIdAsync(),
action: "revoke",
data: {},
filter: {
tokenId: tokenRecord.id,
}
}, {});
}
await context.commit(); await context.commit();
// 5. RFC 7009 规定:令牌撤销成功或令牌无效时,返回 HTTP 200
return { return {
statusCode: 401, statusCode: 200,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false } data: {}
}; };
} }
// 3. 查找令牌记录 catch (err) {
let tokenRecord = null; console.error("Error in oauth token revocation endpoint:", err);
const tokenProjection = { await context.rollback();
data: { id: 1, code: { oauthAppId: 1 } }, return {
filter: {} statusCode: 500,
}; data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
// 尝试查找 Refresh Token };
if (!token_type_hint || token_type_hint === 'refresh_token') {
tokenProjection.filter = { refreshToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
} }
// 如果没找到,且 hint 不是 'refresh_token',则尝试查找 Access Token
if (!tokenRecord && (!token_type_hint || token_type_hint === 'access_token')) {
tokenProjection.filter = { accessToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
}
// 4. 撤销操作(无论找到与否,都返回 200但如果找到则执行失效操作
if (tokenRecord) {
// const pastTime = Date.now() - 1000;
// // 将 Access Token 和 Refresh Token 的过期时间都设为过去,使其立即失效
// await context.operate("oauthToken", {
// id: await generateNewIdAsync(),
// action: "update",
// data: {
// accessExpiresAt: pastTime,
// refreshExpiresAt: pastTime,
// },
// filter: {
// id: tokenRecord.id,
// }
// }, {});
// 使用这个token的认证记录都撤销掉在trigger里会自动设置 revokedAt
await context.operate("oauthUserAuthorization", {
id: await generateNewIdAsync(),
action: "revoke",
data: {},
filter: {
tokenId: tokenRecord.id,
}
}, {});
}
await context.commit();
// 5. RFC 7009 规定:令牌撤销成功或令牌无效时,返回 HTTP 200
return {
statusCode: 200,
data: {}
};
} }
}; };
const endpoints = { const endpoints = {

View File

@ -3,9 +3,6 @@ import { applicationProjection, extraFileProjection } from "../../types/Projecti
import { getDefaultHandlers } from "./handler"; import { getDefaultHandlers } from "./handler";
const handlerMap = new Map(); const handlerMap = new Map();
export const registerOauthUserinfoHandler = (type, handler) => { export const registerOauthUserinfoHandler = (type, handler) => {
if (handlerMap.has(type)) {
throw new Error(`oauth provider type ${type} 的 userinfo 处理器已注册`);
}
handlerMap.set(type, handler); handlerMap.set(type, handler);
}; };
export const processUserInfo = (type, data) => { export const processUserInfo = (type, data) => {

View File

@ -127,7 +127,7 @@ async function loginByOauth(params, context) {
const { user } = existingOAuthUser; const { user } = existingOAuthUser;
const targetUser = user?.userState === 'merged' ? user.ref : user; const targetUser = user?.userState === 'merged' ? user.ref : user;
const tokenValue = await (0, token_1.setUpTokenAndUser)(env, context, 'oauthUser', existingOAuthUser.id, // 使用已存在的 oauthUser ID const tokenValue = await (0, token_1.setUpTokenAndUser)(env, context, 'oauthUser', existingOAuthUser.id, // 使用已存在的 oauthUser ID
undefined, targetUser // 关联的用户 undefined, targetUser // 关联的用户
); );
// 更新登录信息 // 更新登录信息
await context.operate("oauthUser", { await context.operate("oauthUser", {
@ -172,7 +172,7 @@ async function loginByOauth(params, context) {
}; };
// 不传 user 参数,会自动创建新用户 // 不传 user 参数,会自动创建新用户
const tokenValue = await (0, token_1.setUpTokenAndUser)(env, context, 'oauthUser', undefined, oauthUserCreateData, // 创建新的 oauthUser const tokenValue = await (0, token_1.setUpTokenAndUser)(env, context, 'oauthUser', undefined, oauthUserCreateData, // 创建新的 oauthUser
undefined // 不传 user自动创建新用户 undefined // 不传 user自动创建新用户
); );
await context.operate("oauthUser", { await context.operate("oauthUser", {
id: await (0, uuid_1.generateNewIdAsync)(), id: await (0, uuid_1.generateNewIdAsync)(),
@ -367,19 +367,21 @@ async function authorize(params, context) {
throw new Error('unknown action'); throw new Error('unknown action');
} }
const fetchOAuthUserInfo = async (code, providerConfig) => { const fetchOAuthUserInfo = async (code, providerConfig) => {
const params = {
grant_type: 'authorization_code',
code: code,
client_id: providerConfig.clientId,
client_secret: providerConfig.clientSecret,
redirect_uri: providerConfig.redirectUri,
}
console.log("使用 OAuth Code 获取 Access Token:", providerConfig.tokenEndpoint, params);
// 1. 使用 code 换取 access_token // 1. 使用 code 换取 access_token
const tokenResponse = await fetch(providerConfig.tokenEndpoint, { const tokenResponse = await fetch(providerConfig.tokenEndpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
}, },
body: new URLSearchParams({ body: new URLSearchParams(params),
grant_type: 'authorization_code',
code: code,
client_id: providerConfig.clientId,
client_secret: providerConfig.clientSecret,
redirect_uri: providerConfig.redirectUri,
}),
}); });
if (!tokenResponse.ok) { if (!tokenResponse.ok) {
const errorjson = await tokenResponse.json(); const errorjson = await tokenResponse.json();

View File

@ -29,271 +29,284 @@ const oauthTokenEndpoint = {
type: "free", type: "free",
fn: async (contextBuilder, params, header, req, body) => { fn: async (contextBuilder, params, header, req, body) => {
const context = await contextBuilder(); const context = await contextBuilder();
const { client_id, client_secret, grant_type, code, redirect_uri, code_verifier // PKCE支持 try {
} = body; const { client_id, client_secret, grant_type, code, redirect_uri, code_verifier // PKCE支持
const [app] = await context.select("oauthApplication", { } = body;
data: { const [app] = await context.select("oauthApplication", {
id: 1,
clientSecret: 1,
},
filter: {
id: client_id,
}
}, {});
if (!app) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_client", error_description: "Client not found", success: false }
};
}
if (app.clientSecret !== client_secret) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_client", error_description: "Client secret mismatch", success: false }
};
}
// grant_type几种类型 目前只支持authorization_code
if (grant_type !== "authorization_code") {
await context.commit();
return {
statusCode: 400,
data: { error: "unsupported_grant_type", error_description: "Only authorization_code grant type is supported", success: false }
};
}
// 找code的记录
const [authCodeRecord] = await context.select("oauthAuthorizationCode", {
data: {
id: 1,
code: 1,
redirectUri: 1,
userId: 1,
expiresAt: 1,
usedAt: 1,
codeChallenge: 1,
codeChallengeMethod: 1,
},
filter: {
code,
}
}, {});
// 找不到记录
if (!authCodeRecord) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Invalid authorization code", success: false }
};
}
// PKCE 验证
if (authCodeRecord.codeChallenge) {
if (!code_verifier) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_request",
error_description: "code_verifier is required",
success: false
}
};
}
// 验证 code_verifier
const isValid = await verifyPKCE(code_verifier, authCodeRecord.codeChallenge, authCodeRecord.codeChallengeMethod);
if (!isValid) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_grant",
error_description: "Invalid code_verifier",
success: false
}
};
}
}
// 验证redirect_uri
if (authCodeRecord.redirectUri !== redirect_uri) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Redirect URI mismatch", success: false }
};
}
// 验证过期
if (authCodeRecord.expiresAt < Date.now()) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Authorization code expired", success: false }
};
}
// 验证是否已使用
if (authCodeRecord.usedAt) {
await context.commit();
return {
statusCode: 400,
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: { data: {
lastUsedAt: Date.now(), id: 1,
clientSecret: 1,
}, },
filter: { filter: {
id: existingToken.id, id: client_id,
} }
}, {}); }, {});
// // 创建记录 if (!app) {
// await context.operate("oauthUserAuthorization", { await context.commit();
// id: await generateNewIdAsync(), return {
// action: "update", statusCode: 400,
// data: { data: { error: "invalid_client", error_description: "Client not found", success: false }
// tokenId: existingToken.id, };
// usageState: 'granted', }
// }, if (app.clientSecret !== client_secret) {
// filter: { await context.commit();
// codeId: authCodeRecord.id, return {
// } statusCode: 400,
// }, {}) data: { error: "invalid_client", error_description: "Client secret mismatch", success: false }
await context.commit(); };
return { }
statusCode: 200, // grant_type几种类型 目前只支持authorization_code
if (grant_type !== "authorization_code") {
await context.commit();
return {
statusCode: 400,
data: { error: "unsupported_grant_type", error_description: "Only authorization_code grant type is supported", success: false }
};
}
// 找code的记录
const [authCodeRecord] = await context.select("oauthAuthorizationCode", {
data: { data: {
access_token: existingToken.accessToken, id: 1,
token_type: "Bearer", code: 1,
expires_in: existingToken.accessExpiresAt - Date.now(), redirectUri: 1,
refresh_token: existingToken.refreshToken, userId: 1,
refresh_expires_in: existingToken.refreshExpiresAt - Date.now(), expiresAt: 1,
success: true, usedAt: 1,
codeChallenge: 1,
codeChallengeMethod: 1,
},
filter: {
code,
} }
}; }, {});
} // 找不到记录
const expiresIn = 3600; // 1 hour if (!authCodeRecord) {
const refreshTokenExpiresIn = 86400 * 30; // 30 days await context.commit();
// 如果过期就顺带刷新了(refresh token没过期 accessToken过期了) return {
if (existingToken && (existingToken.refreshExpiresAt > Date.now())) { statusCode: 400,
console.log("Existing token expired, refreshing it"); data: { error: "invalid_grant", error_description: "Invalid authorization code", success: false }
const newAccessToken = (0, crypto_1.randomUUID)(); };
const newRefreshToken = (0, crypto_1.randomUUID)(); }
// PKCE 验证
if (authCodeRecord.codeChallenge) {
if (!code_verifier) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_request",
error_description: "code_verifier is required",
success: false
}
};
}
// 验证 code_verifier
const isValid = await verifyPKCE(code_verifier, authCodeRecord.codeChallenge, authCodeRecord.codeChallengeMethod);
if (!isValid) {
await context.commit();
return {
statusCode: 400,
data: {
error: "invalid_grant",
error_description: "Invalid code_verifier",
success: false
}
};
}
}
// 验证redirect_uri
if (authCodeRecord.redirectUri !== redirect_uri) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Redirect URI mismatch", success: false }
};
}
// 验证过期
if (authCodeRecord.expiresAt < Date.now()) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Authorization code expired", success: false }
};
}
// 验证是否已使用
if (authCodeRecord.usedAt) {
await context.commit();
return {
statusCode: 400,
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
// 如果过期就顺带刷新了(refresh token没过期 accessToken过期了)
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,
},
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: 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 refreshToken = (0, crypto_1.randomUUID)();
console.log("Creating new access token and refresh token");
// create
const tokenId = await (0, uuid_1.generateNewIdAsync)();
await context.operate("oauthToken", { await context.operate("oauthToken", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "create",
data: {
id: tokenId,
accessToken: genaccessToken,
refreshToken: refreshToken,
userId: authCodeRecord.userId,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
codeId: authCodeRecord.id,
}
}, {});
// 创建记录
await context.operate("oauthUserAuthorization", {
id: await (0, uuid_1.generateNewIdAsync)(), id: await (0, uuid_1.generateNewIdAsync)(),
action: "update", action: "update",
data: { data: {
lastUsedAt: Date.now(), tokenId: tokenId,
accessToken: newAccessToken, usageState: 'granted',
refreshToken: newRefreshToken, },
accessExpiresAt: Date.now() + expiresIn * 1000, filter: {
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, codeId: authCodeRecord.id,
}
}, {});
// 标记code为已使用
await context.operate("oauthAuthorizationCode", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "update",
data: {
usedAt: Date.now(),
},
filter: {
id: authCodeRecord.id,
} }
}, {}); }, {});
// // 创建记录
// await context.operate("oauthUserAuthorization", {
// id: await generateNewIdAsync(),
// action: "update",
// data: {
// tokenId: existingToken.id,
// usageState: 'granted',
// },
// filter: {
// codeId: authCodeRecord.id,
// }
// }, {})
await context.commit(); await context.commit();
return { return {
statusCode: 200, statusCode: 200,
data: { data: {
access_token: newAccessToken, access_token: genaccessToken,
token_type: "Bearer", token_type: "Bearer",
expires_in: expiresIn, expires_in: expiresIn,
refresh_token: newRefreshToken, refresh_token: refreshToken,
refresh_expires_in: refreshTokenExpiresIn, refresh_expires_in: refreshTokenExpiresIn,
success: true, success: true,
} }
}; };
} }
// 有一种情况是access和refresh都过期了但是用户又重新用code来换token catch (err) {
// 这种情况下不能刷新老的,也要当作新的处理 console.error("Error in oauth token endpoint:", err);
// 创建accessToken await context.rollback();
const genaccessToken = (0, crypto_1.randomUUID)(); return {
const refreshToken = (0, crypto_1.randomUUID)(); statusCode: 500,
console.log("Creating new access token and refresh token"); data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
// create };
const tokenId = await (0, uuid_1.generateNewIdAsync)(); }
await context.operate("oauthToken", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "create",
data: {
id: tokenId,
accessToken: genaccessToken,
refreshToken: refreshToken,
userId: authCodeRecord.userId,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
codeId: authCodeRecord.id,
}
}, {});
// 创建记录
await context.operate("oauthUserAuthorization", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "update",
data: {
tokenId: tokenId,
usageState: 'granted',
},
filter: {
codeId: authCodeRecord.id,
}
}, {});
// 标记code为已使用
await context.operate("oauthAuthorizationCode", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "update",
data: {
usedAt: Date.now(),
},
filter: {
id: authCodeRecord.id,
}
}, {});
await context.commit();
return {
statusCode: 200,
data: {
access_token: genaccessToken,
token_type: "Bearer",
expires_in: expiresIn,
refresh_token: refreshToken,
refresh_expires_in: refreshTokenExpiresIn,
success: true,
}
};
} }
}; };
const oauthUserInfoEndpoint = { const oauthUserInfoEndpoint = {
@ -304,37 +317,47 @@ const oauthUserInfoEndpoint = {
fn: async (contextBuilder, params, header, req, body) => { fn: async (contextBuilder, params, header, req, body) => {
const context = await contextBuilder(); const context = await contextBuilder();
const token = header.authorization; // Bearer token const token = header.authorization; // Bearer token
const checkResult = await (0, oauth_1.checkOauthTokenAvaliable)(context, token); try {
if (checkResult.error) { const checkResult = await (0, oauth_1.checkOauthTokenAvaliable)(context, token);
if (checkResult.error) {
await context.commit();
return {
statusCode: checkResult.statusCode || 401,
data: { error: checkResult.error, success: false }
};
}
const tokenRecord = checkResult.tokenRecord;
(0, assert_1.default)(tokenRecord?.user, "User must be present in token record");
(0, assert_1.default)(tokenRecord?.code?.application, "Application must be present in token record");
const extrafile = tokenRecord.user.extraFile$entity?.[0];
const application = tokenRecord.code.application;
let avatarUrl = '';
if (extrafile) {
avatarUrl = await (0, index_backend_1.composeFileUrlBackend)(application, extrafile, context);
}
await context.commit(); await context.commit();
return { return {
statusCode: checkResult.statusCode || 401, statusCode: 200, data: {
data: { error: checkResult.error, success: false } userInfo: {
id: tokenRecord.user.id,
name: tokenRecord.user.name,
nickname: tokenRecord.user.nickname,
birth: tokenRecord.user.birth,
gender: tokenRecord.user.gender,
avatarUrl: avatarUrl,
},
error: null
}
}; };
} }
const tokenRecord = checkResult.tokenRecord; catch (err) {
(0, assert_1.default)(tokenRecord?.user, "User must be present in token record"); console.error("Error in oauth userinfo endpoint:", err);
(0, assert_1.default)(tokenRecord?.code?.application, "Application must be present in token record"); await context.rollback();
const extrafile = tokenRecord.user.extraFile$entity?.[0]; return {
const application = tokenRecord.code.application; statusCode: 500,
let avatarUrl = ''; data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
if (extrafile) { };
avatarUrl = await (0, index_backend_1.composeFileUrlBackend)(application, extrafile, context);
} }
await context.commit();
return {
statusCode: 200, data: {
userInfo: {
id: tokenRecord.user.id,
name: tokenRecord.user.name,
nickname: tokenRecord.user.nickname,
birth: tokenRecord.user.birth,
gender: tokenRecord.user.gender,
avatarUrl: avatarUrl,
},
error: null
}
};
} }
}; };
const refreshTokenEndpoint = { const refreshTokenEndpoint = {
@ -369,88 +392,98 @@ const refreshTokenEndpoint = {
}; };
} }
const context = await contextBuilder(); const context = await contextBuilder();
const [oauthApp] = await context.select("oauthApplication", { try {
data: { const [oauthApp] = await context.select("oauthApplication", {
id: 1, data: {
}, id: 1,
filter: { },
clientSecret: client_secret, filter: {
id: client_id, clientSecret: client_secret,
id: client_id,
}
}, {});
if (!oauthApp) {
await context.commit();
return {
statusCode: 401,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false }
};
} }
}, {}); const [tokenRecord] = await context.select("oauthToken", {
if (!oauthApp) { data: {
await context.commit(); id: 1,
return { userId: 1,
statusCode: 401, accessToken: 1,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false } accessExpiresAt: 1,
}; refreshToken: 1,
} refreshExpiresAt: 1,
const [tokenRecord] = await context.select("oauthToken", { code: {
data: { applicationId: 1,
id: 1, oauthApp: {
userId: 1, id: 1,
accessToken: 1, }
accessExpiresAt: 1, }
refreshToken: 1, },
refreshExpiresAt: 1, filter: {
code: { refreshToken: refresh_token,
applicationId: 1, code: {
oauthApp: { oauthAppId: oauthApp.id,
id: 1,
} }
} }
}, }, {});
filter: { if (!tokenRecord) {
refreshToken: refresh_token, await context.commit();
code: { return {
oauthAppId: oauthApp.id, statusCode: 400,
data: { error: "invalid_grant", error_description: "Invalid refresh token", success: false }
};
}
if (tokenRecord.refreshExpiresAt < Date.now()) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Refresh token expired", success: false }
};
}
// 生成新的令牌
const newAccessToken = (0, crypto_1.randomUUID)();
const newRefreshToken = (0, crypto_1.randomUUID)();
const expiresIn = 3600; // 1 hour
const refreshTokenExpiresIn = 86400 * 30; // 30 days
await context.operate("oauthToken", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "update",
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
},
filter: {
id: tokenRecord.id,
} }
} }, {});
}, {});
if (!tokenRecord) {
await context.commit(); await context.commit();
return { return {
statusCode: 400, statusCode: 200,
data: { error: "invalid_grant", error_description: "Invalid refresh token", success: false } data: {
access_token: newAccessToken,
token_type: "Bearer",
expires_in: expiresIn,
refresh_token: newRefreshToken,
refresh_expires_in: refreshTokenExpiresIn,
success: true,
}
}; };
} }
if (tokenRecord.refreshExpiresAt < Date.now()) { catch (err) {
await context.commit(); console.error("Error in refresh token endpoint:", err);
await context.rollback();
return { return {
statusCode: 400, statusCode: 500,
data: { error: "invalid_grant", error_description: "Refresh token expired", success: false } data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
}; };
} }
// 生成新的令牌
const newAccessToken = (0, crypto_1.randomUUID)();
const newRefreshToken = (0, crypto_1.randomUUID)();
const expiresIn = 3600; // 1 hour
const refreshTokenExpiresIn = 86400 * 30; // 30 days
await context.operate("oauthToken", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "update",
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessExpiresAt: Date.now() + expiresIn * 1000,
refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000,
},
filter: {
id: tokenRecord.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,
}
};
} }
}; };
const oauthRevocationEndpoint = { const oauthRevocationEndpoint = {
@ -478,64 +511,74 @@ const oauthRevocationEndpoint = {
}; };
} }
const context = await contextBuilder(); const context = await contextBuilder();
const [oauthApp] = await context.select("oauthApplication", { try {
data: { id: 1 }, const [oauthApp] = await context.select("oauthApplication", {
filter: { clientSecret: client_secret, id: client_id } data: { id: 1 },
}, {}); filter: { clientSecret: client_secret, id: client_id }
if (!oauthApp) { }, {});
if (!oauthApp) {
await context.commit();
return {
statusCode: 401,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false }
};
}
// 3. 查找令牌记录
let tokenRecord = null;
const tokenProjection = {
data: { id: 1, code: { oauthAppId: 1 } },
filter: {}
};
// 尝试查找 Refresh Token
if (!token_type_hint || token_type_hint === 'refresh_token') {
tokenProjection.filter = { refreshToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
}
// 如果没找到,且 hint 不是 'refresh_token',则尝试查找 Access Token
if (!tokenRecord && (!token_type_hint || token_type_hint === 'access_token')) {
tokenProjection.filter = { accessToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
}
// 4. 撤销操作(无论找到与否,都返回 200但如果找到则执行失效操作
if (tokenRecord) {
// const pastTime = Date.now() - 1000;
// // 将 Access Token 和 Refresh Token 的过期时间都设为过去,使其立即失效
// await context.operate("oauthToken", {
// id: await generateNewIdAsync(),
// action: "update",
// data: {
// accessExpiresAt: pastTime,
// refreshExpiresAt: pastTime,
// },
// filter: {
// id: tokenRecord.id,
// }
// }, {});
// 使用这个token的认证记录都撤销掉在trigger里会自动设置 revokedAt
await context.operate("oauthUserAuthorization", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "revoke",
data: {},
filter: {
tokenId: tokenRecord.id,
}
}, {});
}
await context.commit(); await context.commit();
// 5. RFC 7009 规定:令牌撤销成功或令牌无效时,返回 HTTP 200
return { return {
statusCode: 401, statusCode: 200,
data: { error: "invalid_client", error_description: "Client authentication failed", success: false } data: {}
}; };
} }
// 3. 查找令牌记录 catch (err) {
let tokenRecord = null; console.error("Error in oauth token revocation endpoint:", err);
const tokenProjection = { await context.rollback();
data: { id: 1, code: { oauthAppId: 1 } }, return {
filter: {} statusCode: 500,
}; data: { error: "server_error", error_description: "Internal server error: " + (err instanceof Error ? err.message : String(err)), success: false }
// 尝试查找 Refresh Token };
if (!token_type_hint || token_type_hint === 'refresh_token') {
tokenProjection.filter = { refreshToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
} }
// 如果没找到,且 hint 不是 'refresh_token',则尝试查找 Access Token
if (!tokenRecord && (!token_type_hint || token_type_hint === 'access_token')) {
tokenProjection.filter = { accessToken: token, code: { oauthAppId: oauthApp.id } };
[tokenRecord] = await context.select("oauthToken", tokenProjection, {});
}
// 4. 撤销操作(无论找到与否,都返回 200但如果找到则执行失效操作
if (tokenRecord) {
// const pastTime = Date.now() - 1000;
// // 将 Access Token 和 Refresh Token 的过期时间都设为过去,使其立即失效
// await context.operate("oauthToken", {
// id: await generateNewIdAsync(),
// action: "update",
// data: {
// accessExpiresAt: pastTime,
// refreshExpiresAt: pastTime,
// },
// filter: {
// id: tokenRecord.id,
// }
// }, {});
// 使用这个token的认证记录都撤销掉在trigger里会自动设置 revokedAt
await context.operate("oauthUserAuthorization", {
id: await (0, uuid_1.generateNewIdAsync)(),
action: "revoke",
data: {},
filter: {
tokenId: tokenRecord.id,
}
}, {});
}
await context.commit();
// 5. RFC 7009 规定:令牌撤销成功或令牌无效时,返回 HTTP 200
return {
statusCode: 200,
data: {}
};
} }
}; };
const endpoints = { const endpoints = {

View File

@ -7,9 +7,6 @@ const Projection_1 = require("../../types/Projection");
const handler_1 = require("./handler"); const handler_1 = require("./handler");
const handlerMap = new Map(); const handlerMap = new Map();
const registerOauthUserinfoHandler = (type, handler) => { const registerOauthUserinfoHandler = (type, handler) => {
if (handlerMap.has(type)) {
throw new Error(`oauth provider type ${type} 的 userinfo 处理器已注册`);
}
handlerMap.set(type, handler); handlerMap.set(type, handler);
}; };
exports.registerOauthUserinfoHandler = registerOauthUserinfoHandler; exports.registerOauthUserinfoHandler = registerOauthUserinfoHandler;

File diff suppressed because it is too large Load Diff

View File

@ -18,9 +18,6 @@ export type UserInfoHandler = (data: UserInfo) => UserID | Promise<UserID>
const handlerMap = new Map<EntityDict['oauthProvider']['Schema']['type'], UserInfoHandler>(); const handlerMap = new Map<EntityDict['oauthProvider']['Schema']['type'], UserInfoHandler>();
export const registerOauthUserinfoHandler = (type: EntityDict['oauthProvider']['Schema']['type'], handler: UserInfoHandler) => { export const registerOauthUserinfoHandler = (type: EntityDict['oauthProvider']['Schema']['type'], handler: UserInfoHandler) => {
if (handlerMap.has(type)) {
throw new Error(`oauth provider type ${type} 的 userinfo 处理器已注册`);
}
handlerMap.set(type, handler); handlerMap.set(type, handler);
} }
@ -38,7 +35,6 @@ Object.entries(defaulthandlers).forEach(([type, handler]) => {
registerOauthUserinfoHandler(type as EntityDict['oauthProvider']['Schema']['type'], handler); registerOauthUserinfoHandler(type as EntityDict['oauthProvider']['Schema']['type'], handler);
}); });
function validateToken(token: string | undefined): { function validateToken(token: string | undefined): {
token: string; token: string;
error: string | null; error: string | null;