| 23 | const CHROMIUM_IDENTITY_HOST_SUFFIX = ".chromiumapp.org"; |
| 24 | |
| 25 | const validateExtensionRedirectUri = (redirectUri: string) => |
| 26 | Effect.gen(function* () { |
| 27 | const url = yield* Effect.try({ |
| 28 | try: () => new URL(redirectUri), |
| 29 | catch: () => new HttpApiError.BadRequest(), |
| 30 | }); |
| 31 | |
| 32 | if ( |
| 33 | url.protocol !== "https:" || |
| 34 | !url.hostname.endsWith(CHROMIUM_IDENTITY_HOST_SUFFIX) |
| 35 | ) { |
| 36 | return yield* new HttpApiError.BadRequest(); |
| 37 | } |
| 38 | |
| 39 | const extensionId = url.hostname.slice( |
| 40 | 0, |
| 41 | -CHROMIUM_IDENTITY_HOST_SUFFIX.length, |
| 42 | ); |
| 43 | const configuredExtensionId = serverEnv().CAP_CHROME_EXTENSION_ID; |
| 44 | |
| 45 | if (configuredExtensionId) { |
| 46 | if (extensionId !== configuredExtensionId) { |
| 47 | return yield* new HttpApiError.BadRequest(); |
| 48 | } |
| 49 | return url; |
| 50 | } |
| 51 | |
| 52 | // Without a pinned extension id, any installed extension could mint a |
| 53 | // signed-in user's auth key through this flow. The only deployment |
| 54 | // where accepting an arbitrary id is safe is localhost development; |
| 55 | // every reachable deployment (staging, previews, self-hosted) must set |
| 56 | // CAP_CHROME_EXTENSION_ID regardless of NODE_ENV. |
| 57 | const webHostname = new URL(serverEnv().WEB_URL).hostname; |
| 58 | const isLocalDevelopment = |
| 59 | serverEnv().NODE_ENV !== "production" && |
| 60 | (webHostname === "localhost" || webHostname === "127.0.0.1"); |
| 61 | |
| 62 | if (!isLocalDevelopment) { |
| 63 | return yield* new HttpApiError.BadRequest(); |
| 64 | } |
| 65 | |
| 66 | return url; |
| 67 | }); |
| 68 | |
| 69 | const redirectToLogin = (nextUrl: URL) => { |
| 70 | const loginUrl = new URL("/login", serverEnv().WEB_URL); |