Migrate Discourse SSO authentication to use Deno's native crypto and encoding modules

This commit is contained in:
wood chen 2025-02-08 01:29:27 +08:00
parent f4e7d16c90
commit ec53c5ec17
2 changed files with 54 additions and 12 deletions

16
deno.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"allowJs": true,
"lib": ["deno.window"],
"strict": true
},
"importMap": {
"imports": {
"std/": "https://deno.land/std@0.220.1/"
}
},
"tasks": {
"start": "deno run --allow-net --allow-env --allow-read main.ts",
"dev": "deno run --watch --allow-net --allow-env --allow-read main.ts"
}
}

50
main.ts
View File

@ -1,4 +1,6 @@
import { serve } from "https://deno.land/std@0.220.1/http/server.ts"; import { serve } from "std/http/server.ts";
import { crypto } from "std/crypto/mod.ts";
import { encode as base64Encode, decode as base64Decode } from "std/encoding/base64.ts";
import { createHmac } from "crypto"; import { createHmac } from "crypto";
// 类型定义 // 类型定义
@ -113,12 +115,24 @@ async function verifyDiscourseSSO(request: Request): Promise<string | null> {
return session.value.username; return session.value.username;
} }
// 添加登录和登出函数 // 修改 generateSSO 函数为异步函数
function generateSSO(returnUrl: string): string { async function generateSSO(returnUrl: string): Promise<string> {
const payload = Buffer.from(`return_sso_url=${encodeURIComponent(returnUrl)}`).toString('base64'); const payload = base64Encode(`return_sso_url=${encodeURIComponent(returnUrl)}`);
const sig = createHmac('sha256', DISCOURSE_SSO_SECRET) const key = await crypto.subtle.importKey(
.update(payload) "raw",
.digest('hex'); new TextEncoder().encode(DISCOURSE_SSO_SECRET),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign(
"HMAC",
key,
new TextEncoder().encode(payload)
);
const sig = Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
return `${DISCOURSE_URL}/session/sso_provider?sso=${encodeURIComponent(payload)}&sig=${sig}`; return `${DISCOURSE_URL}/session/sso_provider?sso=${encodeURIComponent(payload)}&sig=${sig}`;
} }
@ -605,7 +619,7 @@ async function handler(req: Request): Promise<Response> {
}); });
} }
const ssoUrl = generateSSO(returnUrl); const ssoUrl = await generateSSO(returnUrl);
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {
@ -633,16 +647,28 @@ async function handler(req: Request): Promise<Response> {
try { try {
// 验证签名 // 验证签名
const expectedSig = createHmac('sha256', DISCOURSE_SSO_SECRET) const key = await crypto.subtle.importKey(
.update(sso) "raw",
.digest('hex'); new TextEncoder().encode(DISCOURSE_SSO_SECRET),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign(
"HMAC",
key,
new TextEncoder().encode(sso)
);
const expectedSig = Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
if (sig !== expectedSig) { if (sig !== expectedSig) {
throw new Error('Invalid signature'); throw new Error('Invalid signature');
} }
// 解码 payload // 解码 payload
const payload = Buffer.from(sso, 'base64').toString(); const payload = new TextDecoder().decode(base64Decode(sso));
const payloadParams = new URLSearchParams(payload); const payloadParams = new URLSearchParams(payload);
const username = payloadParams.get('username'); const username = payloadParams.get('username');