Replace eslint and prettier with Biome

This commit is contained in:
Jesse Wierzbinski 2024-04-06 19:30:49 -10:00
parent 4a5a2ea590
commit af0d627f19
No known key found for this signature in database
199 changed files with 16493 additions and 16361 deletions

View file

@ -1,95 +1,91 @@
import { apiRoute, applyConfig } from "@api";
import { oauthRedirectUri } from "@constants";
import {
calculatePKCECodeChallenge,
discoveryRequest,
generateRandomCodeVerifier,
processDiscoveryResponse,
calculatePKCECodeChallenge,
discoveryRequest,
generateRandomCodeVerifier,
processDiscoveryResponse,
} from "oauth4webapi";
import { client } from "~database/datasource";
export const meta = applyConfig({
allowedMethods: ["GET"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 20,
},
route: "/oauth/authorize-external",
allowedMethods: ["GET"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 20,
},
route: "/oauth/authorize-external",
});
/**
* Redirects the user to the external OAuth provider
*/
export default apiRoute(async (req, matchedRoute, extraData) => {
const redirectToLogin = (error: string) =>
Response.redirect(
`/oauth/authorize?` +
new URLSearchParams({
...matchedRoute.query,
error: encodeURIComponent(error),
}).toString(),
302
);
const redirectToLogin = (error: string) =>
Response.redirect(
`/oauth/authorize?${new URLSearchParams({
...matchedRoute.query,
error: encodeURIComponent(error),
}).toString()}`,
302,
);
const issuerId = matchedRoute.query.issuer;
const issuerId = matchedRoute.query.issuer;
// This is the Lysand client's client_id, not the external OAuth provider's client_id
const clientId = matchedRoute.query.clientId;
// This is the Lysand client's client_id, not the external OAuth provider's client_id
const clientId = matchedRoute.query.clientId;
if (!clientId || clientId === "undefined") {
return redirectToLogin("Missing client_id");
}
if (!clientId || clientId === "undefined") {
return redirectToLogin("Missing client_id");
}
const config = await extraData.configManager.getConfig();
const config = await extraData.configManager.getConfig();
const issuer = config.oidc.providers.find(
provider => provider.id === issuerId
);
const issuer = config.oidc.providers.find(
(provider) => provider.id === issuerId,
);
if (!issuer) {
return redirectToLogin("Invalid issuer");
}
if (!issuer) {
return redirectToLogin("Invalid issuer");
}
const issuerUrl = new URL(issuer.url);
const issuerUrl = new URL(issuer.url);
const authServer = await discoveryRequest(issuerUrl, {
algorithm: "oidc",
}).then(res => processDiscoveryResponse(issuerUrl, res));
const authServer = await discoveryRequest(issuerUrl, {
algorithm: "oidc",
}).then((res) => processDiscoveryResponse(issuerUrl, res));
const codeVerifier = generateRandomCodeVerifier();
const codeVerifier = generateRandomCodeVerifier();
// Store into database
// Store into database
const newFlow = await client.openIdLoginFlow.create({
data: {
codeVerifier,
application: {
connect: {
client_id: clientId,
},
},
issuerId,
},
});
const newFlow = await client.openIdLoginFlow.create({
data: {
codeVerifier,
application: {
connect: {
client_id: clientId,
},
},
issuerId,
},
});
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
return Response.redirect(
authServer.authorization_endpoint +
"?" +
new URLSearchParams({
client_id: issuer.client_id,
redirect_uri:
oauthRedirectUri(issuerId) + `?flow=${newFlow.id}`,
response_type: "code",
scope: "openid profile email",
// PKCE
code_challenge_method: "S256",
code_challenge: codeChallenge,
}).toString(),
302
);
return Response.redirect(
`${authServer.authorization_endpoint}?${new URLSearchParams({
client_id: issuer.client_id,
redirect_uri: `${oauthRedirectUri(issuerId)}?flow=${newFlow.id}`,
response_type: "code",
scope: "openid profile email",
// PKCE
code_challenge_method: "S256",
code_challenge: codeChallenge,
}).toString()}`,
302,
);
});

View file

@ -1,198 +1,196 @@
import { randomBytes } from "node:crypto";
import { apiRoute, applyConfig } from "@api";
import { oauthRedirectUri } from "@constants";
import { randomBytes } from "crypto";
import {
authorizationCodeGrantRequest,
discoveryRequest,
expectNoState,
isOAuth2Error,
processDiscoveryResponse,
validateAuthResponse,
userInfoRequest,
processAuthorizationCodeOpenIDResponse,
processUserInfoResponse,
getValidatedIdTokenClaims,
authorizationCodeGrantRequest,
discoveryRequest,
expectNoState,
getValidatedIdTokenClaims,
isOAuth2Error,
processAuthorizationCodeOpenIDResponse,
processDiscoveryResponse,
processUserInfoResponse,
userInfoRequest,
validateAuthResponse,
} from "oauth4webapi";
import { client } from "~database/datasource";
import { TokenType } from "~database/entities/Token";
export const meta = applyConfig({
allowedMethods: ["GET"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 20,
},
route: "/oauth/callback/:issuer",
allowedMethods: ["GET"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 20,
},
route: "/oauth/callback/:issuer",
});
/**
* Redirects the user to the external OAuth provider
*/
export default apiRoute(async (req, matchedRoute, extraData) => {
const redirectToLogin = (error: string) =>
Response.redirect(
`/oauth/authorize?` +
new URLSearchParams({
client_id: matchedRoute.query.clientId,
error: encodeURIComponent(error),
}).toString(),
302
);
const redirectToLogin = (error: string) =>
Response.redirect(
`/oauth/authorize?${new URLSearchParams({
client_id: matchedRoute.query.clientId,
error: encodeURIComponent(error),
}).toString()}`,
302,
);
const currentUrl = new URL(req.url);
const currentUrl = new URL(req.url);
// Remove state query parameter from URL
currentUrl.searchParams.delete("state");
const issuerParam = matchedRoute.params.issuer;
const flow = await client.openIdLoginFlow.findFirst({
where: {
id: matchedRoute.query.flow,
},
include: {
application: true,
},
});
// Remove state query parameter from URL
currentUrl.searchParams.delete("state");
const issuerParam = matchedRoute.params.issuer;
const flow = await client.openIdLoginFlow.findFirst({
where: {
id: matchedRoute.query.flow,
},
include: {
application: true,
},
});
if (!flow) {
return redirectToLogin("Invalid flow");
}
if (!flow) {
return redirectToLogin("Invalid flow");
}
const config = await extraData.configManager.getConfig();
const config = await extraData.configManager.getConfig();
const issuer = config.oidc.providers.find(
provider => provider.id === issuerParam
);
const issuer = config.oidc.providers.find(
(provider) => provider.id === issuerParam,
);
if (!issuer) {
return redirectToLogin("Invalid issuer");
}
if (!issuer) {
return redirectToLogin("Invalid issuer");
}
const issuerUrl = new URL(issuer.url);
const issuerUrl = new URL(issuer.url);
const authServer = await discoveryRequest(issuerUrl, {
algorithm: "oidc",
}).then(res => processDiscoveryResponse(issuerUrl, res));
const authServer = await discoveryRequest(issuerUrl, {
algorithm: "oidc",
}).then((res) => processDiscoveryResponse(issuerUrl, res));
const parameters = validateAuthResponse(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
currentUrl,
// Whether to expect state or not
expectNoState
);
const parameters = validateAuthResponse(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
currentUrl,
// Whether to expect state or not
expectNoState,
);
if (isOAuth2Error(parameters)) {
return redirectToLogin(
parameters.error_description || parameters.error
);
}
if (isOAuth2Error(parameters)) {
return redirectToLogin(
parameters.error_description || parameters.error,
);
}
const response = await authorizationCodeGrantRequest(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
parameters,
oauthRedirectUri(issuerParam) + `?flow=${flow.id}`,
flow.codeVerifier
);
const response = await authorizationCodeGrantRequest(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
parameters,
`${oauthRedirectUri(issuerParam)}?flow=${flow.id}`,
flow.codeVerifier,
);
const result = await processAuthorizationCodeOpenIDResponse(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
response
);
const result = await processAuthorizationCodeOpenIDResponse(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
response,
);
if (isOAuth2Error(result)) {
return redirectToLogin(result.error_description || result.error);
}
if (isOAuth2Error(result)) {
return redirectToLogin(result.error_description || result.error);
}
const { access_token } = result;
const { access_token } = result;
const claims = getValidatedIdTokenClaims(result);
const { sub } = claims;
const claims = getValidatedIdTokenClaims(result);
const { sub } = claims;
// Validate `sub`
// Later, we'll use this to automatically set the user's data
await userInfoRequest(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
access_token
).then(res =>
processUserInfoResponse(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
sub,
res
)
);
// Validate `sub`
// Later, we'll use this to automatically set the user's data
await userInfoRequest(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
access_token,
).then((res) =>
processUserInfoResponse(
authServer,
{
client_id: issuer.client_id,
client_secret: issuer.client_secret,
},
sub,
res,
),
);
const user = await client.user.findFirst({
where: {
linkedOpenIdAccounts: {
some: {
serverId: sub,
issuerId: issuer.id,
},
},
},
});
const user = await client.user.findFirst({
where: {
linkedOpenIdAccounts: {
some: {
serverId: sub,
issuerId: issuer.id,
},
},
},
});
if (!user) {
return redirectToLogin("No user found with that account");
}
if (!user) {
return redirectToLogin("No user found with that account");
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!flow.application) return redirectToLogin("Invalid client_id");
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!flow.application) return redirectToLogin("Invalid client_id");
const code = randomBytes(32).toString("hex");
const code = randomBytes(32).toString("hex");
await client.application.update({
where: { id: flow.application.id },
data: {
tokens: {
create: {
access_token: randomBytes(64).toString("base64url"),
code: code,
scope: flow.application.scopes,
token_type: TokenType.BEARER,
user: {
connect: {
id: user.id,
},
},
},
},
},
});
await client.application.update({
where: { id: flow.application.id },
data: {
tokens: {
create: {
access_token: randomBytes(64).toString("base64url"),
code: code,
scope: flow.application.scopes,
token_type: TokenType.BEARER,
user: {
connect: {
id: user.id,
},
},
},
},
},
});
// Redirect back to application
return Response.redirect(
`/oauth/redirect?` +
new URLSearchParams({
redirect_uri: flow.application.redirect_uris,
code,
client_id: flow.application.client_id,
application: flow.application.name,
website: flow.application.website ?? "",
scope: flow.application.scopes,
}).toString(),
302
);
// Redirect back to application
return Response.redirect(
`/oauth/redirect?${new URLSearchParams({
redirect_uri: flow.application.redirect_uris,
code,
client_id: flow.application.client_id,
application: flow.application.name,
website: flow.application.website ?? "",
scope: flow.application.scopes,
}).toString()}`,
302,
);
});

View file

@ -2,28 +2,28 @@ import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
export const meta = applyConfig({
allowedMethods: ["GET"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 10,
},
route: "/oauth/providers",
allowedMethods: ["GET"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 10,
},
route: "/oauth/providers",
});
/**
* Lists available OAuth providers
*/
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
const config = await extraData.configManager.getConfig();
return jsonResponse(
config.oidc.providers.map(p => ({
name: p.name,
icon: p.icon,
id: p.id,
}))
);
return jsonResponse(
config.oidc.providers.map((p) => ({
name: p.name,
icon: p.icon,
id: p.id,
})),
);
});

View file

@ -3,61 +3,61 @@ import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
export const meta = applyConfig({
allowedMethods: ["POST"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 10,
},
route: "/oauth/token",
allowedMethods: ["POST"],
auth: {
required: false,
},
ratelimits: {
duration: 60,
max: 10,
},
route: "/oauth/token",
});
/**
* Allows getting token from OAuth code
*/
export default apiRoute<{
grant_type: string;
code: string;
redirect_uri: string;
client_id: string;
client_secret: string;
scope: string;
grant_type: string;
code: string;
redirect_uri: string;
client_id: string;
client_secret: string;
scope: string;
}>(async (req, matchedRoute, extraData) => {
const { grant_type, code, redirect_uri, client_id, client_secret, scope } =
extraData.parsedRequest;
const { grant_type, code, redirect_uri, client_id, client_secret, scope } =
extraData.parsedRequest;
if (grant_type !== "authorization_code")
return errorResponse(
"Invalid grant type (try 'authorization_code')",
400
);
if (grant_type !== "authorization_code")
return errorResponse(
"Invalid grant type (try 'authorization_code')",
400,
);
// Get associated token
const token = await client.token.findFirst({
where: {
code,
application: {
client_id,
secret: client_secret,
redirect_uris: redirect_uri,
scopes: scope?.replaceAll("+", " "),
},
scope: scope?.replaceAll("+", " "),
},
include: {
application: true,
},
});
// Get associated token
const token = await client.token.findFirst({
where: {
code,
application: {
client_id,
secret: client_secret,
redirect_uris: redirect_uri,
scopes: scope?.replaceAll("+", " "),
},
scope: scope?.replaceAll("+", " "),
},
include: {
application: true,
},
});
if (!token)
return errorResponse("Invalid access token or client credentials", 401);
if (!token)
return errorResponse("Invalid access token or client credentials", 401);
return jsonResponse({
access_token: token.access_token,
token_type: token.token_type,
scope: token.scope,
created_at: token.created_at,
});
return jsonResponse({
access_token: token.access_token,
token_type: token.token_type,
scope: token.scope,
created_at: token.created_at,
});
});