mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 17:08:27 +00:00
* Feat: OAuth support for external apps * Fix: OAuth screen --------- Co-authored-by: Manoj K <saimanoj58@gmail.com>
133 lines
3.3 KiB
TypeScript
133 lines
3.3 KiB
TypeScript
import { findUserByToken } from "~/models/personal-token.server";
|
|
import { oauth2Service } from "~/services/oauth2.server";
|
|
|
|
// See this for more: https://twitter.com/mattpocockuk/status/1653403198885904387?s=20
|
|
export type Prettify<T> = {
|
|
[K in keyof T]: T[K];
|
|
} & {};
|
|
|
|
export type ApiAuthenticationResult =
|
|
| ApiAuthenticationResultSuccess
|
|
| ApiAuthenticationResultFailure;
|
|
|
|
export type ApiAuthenticationResultSuccess = {
|
|
ok: true;
|
|
apiKey: string;
|
|
type: "PRIVATE" | "OAUTH2";
|
|
userId: string;
|
|
scopes?: string[];
|
|
oneTimeUse?: boolean;
|
|
oauth2?: {
|
|
clientId: string;
|
|
scope: string | null;
|
|
};
|
|
};
|
|
|
|
export type ApiAuthenticationResultFailure = {
|
|
ok: false;
|
|
error: string;
|
|
};
|
|
|
|
/**
|
|
* This method is the same as `authenticateApiRequest` but it returns a failure result instead of undefined.
|
|
* It should be used from now on to ensure that the API key is always validated and provide a failure result.
|
|
*/
|
|
export async function authenticateApiRequestWithFailure(
|
|
request: Request,
|
|
options: { allowPublicKey?: boolean; allowJWT?: boolean } = {},
|
|
): Promise<ApiAuthenticationResult> {
|
|
const apiKey = getApiKeyFromRequest(request);
|
|
|
|
if (!apiKey) {
|
|
return {
|
|
ok: false,
|
|
error: "Invalid API Key",
|
|
};
|
|
}
|
|
|
|
const authentication = await authenticateApiKeyWithFailure(apiKey, options);
|
|
|
|
return authentication;
|
|
}
|
|
|
|
/**
|
|
* This method is the same as `authenticateApiKey` but it returns a failure result instead of undefined.
|
|
* It should be used from now on to ensure that the API key is always validated and provide a failure result.
|
|
*/
|
|
export async function authenticateApiKeyWithFailure(
|
|
apiKey: string,
|
|
options: { allowPublicKey?: boolean; allowJWT?: boolean } = {},
|
|
): Promise<ApiAuthenticationResult> {
|
|
// First try OAuth2 access token
|
|
try {
|
|
const accessToken = await oauth2Service.validateAccessToken(apiKey);
|
|
if (accessToken) {
|
|
return {
|
|
ok: true,
|
|
apiKey,
|
|
type: "OAUTH2",
|
|
userId: accessToken.user.id,
|
|
scopes: accessToken.scope ? accessToken.scope.split(' ') : undefined,
|
|
oauth2: {
|
|
clientId: accessToken.client.clientId,
|
|
scope: accessToken.scope,
|
|
},
|
|
};
|
|
}
|
|
} catch (error) {
|
|
// If OAuth2 token validation fails, continue to PAT validation
|
|
}
|
|
|
|
// Fall back to PAT authentication
|
|
const result = getApiKeyResult(apiKey);
|
|
|
|
if (!result) {
|
|
return {
|
|
ok: false,
|
|
error: "Invalid API Key",
|
|
};
|
|
}
|
|
|
|
switch (result.type) {
|
|
case "PRIVATE": {
|
|
const user = await findUserByToken(result.apiKey);
|
|
if (!user) {
|
|
return {
|
|
ok: false,
|
|
error: "Invalid API Key",
|
|
};
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
...result,
|
|
userId: user.userId,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export function isSecretApiKey(key: string) {
|
|
return key.startsWith("rc_");
|
|
}
|
|
|
|
export function getApiKeyFromRequest(request: Request) {
|
|
return getApiKeyFromHeader(request.headers.get("Authorization"));
|
|
}
|
|
|
|
export function getApiKeyFromHeader(authorization?: string | null) {
|
|
if (typeof authorization !== "string" || !authorization) {
|
|
return;
|
|
}
|
|
|
|
const apiKey = authorization.replace(/^Bearer /, "");
|
|
return apiKey;
|
|
}
|
|
|
|
export function getApiKeyResult(apiKey: string): {
|
|
apiKey: string;
|
|
type: "PRIVATE";
|
|
} {
|
|
return { apiKey, type: "PRIVATE" };
|
|
}
|