diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 4ac9614..32f10da 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -2,10 +2,11 @@ import cors from "@elysiajs/cors"; import { staticPlugin } from "@elysiajs/static"; import { swagger } from "@elysiajs/swagger"; import { AttachmentId, PieceId, RepertoireId, RequestId, SessionId, Sha256_Bin, Sha256_Hex } from "common"; -import { Effect, Option, pipe } from "effect"; +import { Effect, Option, pipe, Redacted } from "effect"; import { Elysia, error, t } from "elysia"; import { sql } from "kysely"; -import { CLIENT_ID, EXTERNAL_OAUTH_CONFIGURATION, getUser, INTERNAL_OAUTH_CONFIGURATION, makeAuthorizationUrl, REDIRECT_URI, revalidateTokens } from "./auth"; +import { EXTERNAL_OAUTH_CONFIGURATION, getUser, INTERNAL_OAUTH_CONFIGURATION, makeAuthorizationUrl, REDIRECT_URI, revalidateTokens } from "./auth"; +import { config } from "./config"; import * as Db from "./database"; import * as Model from "./model"; import { DbFromInstance } from "./services/db"; @@ -30,9 +31,9 @@ const app = new Elysia() }, })) - .use(cors({ origin: process.env.NODE_ENV === "production" ? false : "localhost:5173" })) + .use(cors({ origin: config.NODE_ENV === "production" ? false : "localhost:5173" })) - .decorate("db", await Db.initDatabase(process.env.DB_PATH)) + .decorate("db", await Db.initDatabase(config.DB_PATH)) .resolve(async ({ db, cookie }) => { await db @@ -201,12 +202,12 @@ const app = new Elysia() "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ - "client_id": CLIENT_ID, + "client_id": config.CLIENT_ID, "code": code, "redirect_uri": REDIRECT_URI, "grant_type": "authorization_code", "code_verifier": codeVerifier.value, - "client_secret": process.env.CLIENT_SECRET!, + "client_secret": Redacted.value(config.CLIENT_SECRET), }).toString(), }); @@ -229,7 +230,7 @@ const app = new Elysia() .execute(); } - return redirect(process.env.NODE_ENV === "production" ? "https://music.renati.me/" : "http://localhost:5173/", 303) as unknown as void; + return redirect(config.NODE_ENV === "production" ? "https://music.renati.me/" : "http://localhost:5173/", 303) as unknown as void; }, { response: { 303: t.Void(), @@ -859,5 +860,5 @@ const app = new Elysia() // ------------------------------------------------------------------------- -app.listen(process.env.PORT || 3000); +app.listen(config.PORT); export type App = typeof app; diff --git a/packages/backend/src/auth.ts b/packages/backend/src/auth.ts index b116409..710f818 100644 --- a/packages/backend/src/auth.ts +++ b/packages/backend/src/auth.ts @@ -1,17 +1,13 @@ import { UserId } from "common"; -import { DateTime, Duration, Effect, Option, pipe } from "effect"; +import { DateTime, Duration, Effect, Option, pipe, Redacted } from "effect"; import { constant } from "effect/Function"; +import { config } from "./config"; import * as Model from "./model"; import { Db } from "./services/db"; import { Session } from "./services/session"; -export const TENANT_ID = "0817c403-92e4-4648-a9aa-f688ffc5f97a"; -export const TENANT_SUBDOMAIN = "chkvoxastra"; - -export const CLIENT_ID = "e5948f7d-187b-44f9-80cd-63ffda86f9be"; export const OAUTH_SCOPE = "email offline_access openid profile https://graph.microsoft.com/User.Read.All"; - -export const REDIRECT_URI = process.env.NODE_ENV === "production" ? "https://music.renati.me/api/v1/login" : "http://localhost:3000/api/v1/login"; +export const REDIRECT_URI = config.NODE_ENV === "production" ? "https://music.renati.me/api/v1/login" : "http://localhost:3000/api/v1/login"; export const EXPIRATION_BUFFER = Duration.seconds(10); @@ -21,13 +17,13 @@ export interface OAuthConfiguration { } export const INTERNAL_OAUTH_CONFIGURATION: OAuthConfiguration = Object.freeze({ - authorizationEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize`, - tokenEndpoint: `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`, + authorizationEndpoint: `https://login.microsoftonline.com/${config.TENANT_ID}/oauth2/v2.0/authorize`, + tokenEndpoint: `https://login.microsoftonline.com/${config.TENANT_ID}/oauth2/v2.0/token`, }); export const EXTERNAL_OAUTH_CONFIGURATION: OAuthConfiguration = Object.freeze({ - authorizationEndpoint: `https://${TENANT_SUBDOMAIN}.ciamlogin.com/${TENANT_ID}/oauth2/v2.0/authorize`, - tokenEndpoint: `https://${TENANT_SUBDOMAIN}.ciamlogin.com/${TENANT_ID}/oauth2/v2.0/token`, + authorizationEndpoint: `https://${config.TENANT_SUBDOMAIN}.ciamlogin.com/${config.TENANT_ID}/oauth2/v2.0/authorize`, + tokenEndpoint: `https://${config.TENANT_SUBDOMAIN}.ciamlogin.com/${config.TENANT_ID}/oauth2/v2.0/token`, }); export namespace makeAuthorizationUrl { @@ -60,7 +56,7 @@ export const makeAuthorizationUrl = Effect.fn("makeAuthorizationUrl")( const { authorizationEndpoint } = external ? EXTERNAL_OAUTH_CONFIGURATION : INTERNAL_OAUTH_CONFIGURATION; const url = new URL(authorizationEndpoint); - url.searchParams.set("client_id", CLIENT_ID); + url.searchParams.set("client_id", config.CLIENT_ID); url.searchParams.set("response_type", "code"); url.searchParams.set("redirect_uri", REDIRECT_URI); url.searchParams.set("scope", OAUTH_SCOPE); @@ -132,10 +128,10 @@ export const revalidateTokens = Effect.fn("revaildateTokens")( "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ - "client_id": CLIENT_ID, + "client_id": config.CLIENT_ID, "grant_type": "refresh_token", "refresh_token": refreshTokenValue, - "client_secret": process.env.CLIENT_SECRET!, + "client_secret": Redacted.value(config.CLIENT_SECRET), }).toString(), })); diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts new file mode 100644 index 0000000..8031d52 --- /dev/null +++ b/packages/backend/src/config.ts @@ -0,0 +1,33 @@ +import { pipe, Schema } from "effect"; +import { constant } from "effect/Function"; + +/* NOTE I know "effect/Config" exists, but I also don't care. This works for me. */ + +export const Config = Schema.Struct({ + CLIENT_ID: Schema.UUID, + CLIENT_SECRET: pipe( + Schema.String, + Schema.Redacted, + ), + DB_PATH: pipe( + Schema.String, + Schema.optional, + ), + NODE_ENV: pipe( + Schema.Literal("development", "production"), + Schema.optionalWith({ default: constant("development" as const) }), + ), + PORT: pipe( + Schema.NumberFromString, + Schema.optionalWith({ default: constant(3000) }), + ), + TENANT_ID: Schema.UUID, + TENANT_SUBDOMAIN: Schema.String, +}); + +export type Config = typeof Config.Type; + +export const config = pipe( + process.env, + Schema.decodeUnknownSync(Config), +);