Area restrita - Questionario
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import type { Context } from "hono";
|
||||
import { setCookie } from "hono/cookie";
|
||||
import * as jose from "jose";
|
||||
import * as cookie from "cookie";
|
||||
import { env } from "../lib/env";
|
||||
import { getSessionCookieOptions } from "../lib/cookies";
|
||||
import { Session } from "@contracts/constants";
|
||||
import { Errors } from "@contracts/errors";
|
||||
import { signSessionToken, verifySessionToken } from "./session";
|
||||
import { users as kimiUsers } from "./platform";
|
||||
import { findUserByUnionId, upsertUser } from "../queries/users";
|
||||
import type { TokenResponse } from "./types";
|
||||
|
||||
async function exchangeAuthCode(
|
||||
code: string,
|
||||
redirectUri: string,
|
||||
): Promise<TokenResponse> {
|
||||
const body = new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
client_id: env.appId,
|
||||
redirect_uri: redirectUri,
|
||||
client_secret: env.appSecret,
|
||||
});
|
||||
|
||||
const resp = await fetch(`${env.kimiAuthUrl}/api/oauth/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: body.toString(),
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
throw new Error(`Token exchange failed (${resp.status}): ${text}`);
|
||||
}
|
||||
|
||||
return resp.json() as Promise<TokenResponse>;
|
||||
}
|
||||
|
||||
const jwks = jose.createRemoteJWKSet(
|
||||
new URL(`${env.kimiAuthUrl}/api/.well-known/jwks.json`),
|
||||
);
|
||||
|
||||
async function verifyAccessToken(
|
||||
accessToken: string,
|
||||
): Promise<{ userId: string; clientId: string }> {
|
||||
const { payload } = await jose.jwtVerify(accessToken, jwks);
|
||||
const userId = payload.user_id as string;
|
||||
const clientId = payload.client_id as string;
|
||||
if (!userId) {
|
||||
throw new Error("user_id missing from access token");
|
||||
}
|
||||
return { userId, clientId };
|
||||
}
|
||||
|
||||
export async function authenticateRequest(headers: Headers) {
|
||||
const cookies = cookie.parse(headers.get("cookie") || "");
|
||||
const token = cookies[Session.cookieName];
|
||||
if (!token) {
|
||||
console.warn("[auth] No session cookie found in request.");
|
||||
throw Errors.forbidden("Invalid authentication token.");
|
||||
}
|
||||
const claim = await verifySessionToken(token);
|
||||
if (!claim) {
|
||||
throw Errors.forbidden("Invalid authentication token.");
|
||||
}
|
||||
const user = await findUserByUnionId(claim.unionId);
|
||||
if (!user) {
|
||||
throw Errors.forbidden("User not found. Please re-login.");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
export function createOAuthCallbackHandler() {
|
||||
return async (c: Context) => {
|
||||
const code = c.req.query("code");
|
||||
const state = c.req.query("state");
|
||||
const error = c.req.query("error");
|
||||
const errorDescription = c.req.query("error_description");
|
||||
|
||||
if (error) {
|
||||
if (error === "access_denied") {
|
||||
return c.redirect("/", 302);
|
||||
}
|
||||
return c.json(
|
||||
{ error, error_description: errorDescription },
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
if (!code || !state) {
|
||||
return c.json({ error: "code and state are required" }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const redirectUri = atob(state);
|
||||
const tokenResp = await exchangeAuthCode(code, redirectUri);
|
||||
const { userId } = await verifyAccessToken(tokenResp.access_token);
|
||||
const userProfile = await kimiUsers.getProfile(tokenResp.access_token);
|
||||
if (!userProfile) {
|
||||
throw new Error("Failed to fetch user profile from Kimi Open");
|
||||
}
|
||||
|
||||
await upsertUser({
|
||||
unionId: userId,
|
||||
name: userProfile.name,
|
||||
avatar: userProfile.avatar_url,
|
||||
lastSignInAt: new Date(),
|
||||
});
|
||||
|
||||
const token = await signSessionToken({
|
||||
unionId: userId,
|
||||
clientId: env.appId,
|
||||
});
|
||||
|
||||
const cookieOpts = getSessionCookieOptions(c.req.raw.headers);
|
||||
setCookie(c, Session.cookieName, token, {
|
||||
...cookieOpts,
|
||||
maxAge: Session.maxAgeMs / 1000,
|
||||
});
|
||||
|
||||
return c.redirect("/", 302);
|
||||
} catch (error) {
|
||||
console.error("[OAuth] Callback failed", error);
|
||||
return c.json({ error: "OAuth callback failed" }, 500);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { exchangeAuthCode, verifyAccessToken };
|
||||
Reference in New Issue
Block a user