mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 21:38:27 +00:00
112 lines
2.4 KiB
TypeScript
112 lines
2.4 KiB
TypeScript
import { json } from "@remix-run/node";
|
|
import { oauth2Service } from "~/services/oauth2.server";
|
|
|
|
export interface OAuth2Context {
|
|
user: {
|
|
id: string;
|
|
email: string;
|
|
name: string | null;
|
|
displayName: string | null;
|
|
avatarUrl: string | null;
|
|
};
|
|
client: {
|
|
id: string;
|
|
clientId: string;
|
|
name: string;
|
|
};
|
|
token: {
|
|
id: string;
|
|
token: string;
|
|
scope: string | null;
|
|
expiresAt: Date;
|
|
};
|
|
}
|
|
|
|
export async function requireOAuth2(request: Request): Promise<OAuth2Context> {
|
|
const authHeader = request.headers.get("authorization");
|
|
|
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
throw json(
|
|
{
|
|
error: "invalid_token",
|
|
error_description: "Missing or invalid authorization header",
|
|
},
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
|
|
const token = authHeader.substring(7); // Remove "Bearer " prefix
|
|
|
|
try {
|
|
const accessToken = await oauth2Service.validateAccessToken(token);
|
|
|
|
return {
|
|
user: {
|
|
id: accessToken.user.id,
|
|
email: accessToken.user.email,
|
|
name: accessToken.user.name,
|
|
displayName: accessToken.user.displayName,
|
|
avatarUrl: accessToken.user.avatarUrl,
|
|
},
|
|
client: {
|
|
id: accessToken.client.id,
|
|
clientId: accessToken.client.clientId,
|
|
name: accessToken.client.name,
|
|
},
|
|
token: {
|
|
id: accessToken.id,
|
|
token: accessToken.token,
|
|
scope: accessToken.scope,
|
|
expiresAt: accessToken.expiresAt,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
throw json(
|
|
{
|
|
error: "invalid_token",
|
|
error_description: "Invalid or expired access token",
|
|
},
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function getOAuth2Context(
|
|
request: Request,
|
|
): Promise<OAuth2Context | null> {
|
|
try {
|
|
return await requireOAuth2(request);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function hasScope(
|
|
context: OAuth2Context,
|
|
requiredScope: string,
|
|
): boolean {
|
|
if (!context.token.scope) {
|
|
return false;
|
|
}
|
|
|
|
const scopes = context.token.scope.split(" ");
|
|
return scopes.includes(requiredScope);
|
|
}
|
|
|
|
export function requireScope(
|
|
context: OAuth2Context,
|
|
requiredScope: string,
|
|
): void {
|
|
if (!hasScope(context, requiredScope)) {
|
|
throw json(
|
|
{
|
|
error: "insufficient_scope",
|
|
error_description: `Required scope: ${requiredScope}`,
|
|
},
|
|
{ status: 403 },
|
|
);
|
|
}
|
|
}
|
|
|
|
export function getEnvForCommand() {}
|