#!/usr/bin/env node /* * Seed a dev SSO provider + pre-linked identities — DEV ONLY. * * Registers an `auth_providers` row pointing at the local `stub-idp.js`, so * `GET /auth/providers` returns a provider and the native mobile SSO flow becomes * exercisable. Because SSO is link-only (identities are never auto-provisioned), * it also pre-links each stub principal's `sub` to an existing dev account. * * Idempotent: re-running upserts the provider and skips already-linked identities. * * Run (loads website/server/.env for DB creds): * node website/scripts/dev/seed-sso-provider.js * * Env overrides: * STUB_PROVIDER_ID provider slug (default 'devstub') * STUB_IDP_PUBLIC_URL browser-facing base (default http://127.0.0.1:9099) * STUB_IDP_INTERNAL_URL server-facing base (default = STUB_IDP_PUBLIC_URL) * * The authorize URL is followed by the browser (Custom Tab); token/userinfo are * called server-side by the website. On an emulator set PUBLIC to the host's * reachable address (e.g. http://10.0.2.2:9099) and INTERNAL to http://127.0.0.1:9099. */ 'use strict' const fs = require('fs') const path = require('path') // Load website/server/.env into process.env WITHOUT the dotenv dependency (it // lives in server/node_modules and wouldn't resolve from this scripts/ location). // The file is simple KEY=value; that is all we need for the DB credentials. function loadEnv(envPath) { if (!fs.existsSync(envPath)) return for (const line of fs.readFileSync(envPath, 'utf8').split(/\r?\n/)) { const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line) if (!m) continue const key = m[1] let val = m[2].trim() if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { val = val.slice(1, -1) } if (process.env[key] === undefined) process.env[key] = val } } loadEnv(path.join(__dirname, '..', '..', 'server', '.env')) const authProviders = require('../../server/src/model/authProviders/authProviders.model') const userIdentities = require('../../server/src/model/userIdentities/userIdentities.model') const { close } = require('../../server/src/utils/db') const PROVIDER_ID = process.env.STUB_PROVIDER_ID || 'devstub' const PUBLIC_URL = (process.env.STUB_IDP_PUBLIC_URL || 'http://127.0.0.1:9099').replace(/\/$/, '') const INTERNAL_URL = (process.env.STUB_IDP_INTERNAL_URL || PUBLIC_URL).replace(/\/$/, '') // sub → dev account id. Keep the subs in sync with stub-idp.js PRINCIPALS. const PRINCIPALS = [ { sub: 'stub-admin', email: 'admin@dev.local', userId: 1 }, { sub: 'stub-colby', email: 'colby@dev.local', userId: 14 }, ] async function main() { // eslint-disable-next-line no-console const log = (...a) => console.log('[seed-sso]', ...a) await authProviders.save(PROVIDER_ID, { kind: 'oauth2', name: 'Dev Stub IdP', enabled: true, clientId: 'stub-client', secret: 'stub-secret', authorizeUrl: `${PUBLIC_URL}/authorize`, tokenUrl: `${INTERNAL_URL}/token`, userinfoUrl: `${INTERNAL_URL}/userinfo`, scopes: 'openid email profile', priority: 50, }) log(`provider '${PROVIDER_ID}' upserted (authorize=${PUBLIC_URL}/authorize, token/userinfo=${INTERNAL_URL})`) for (const p of PRINCIPALS) { const existing = await userIdentities.findByProviderSubject(PROVIDER_ID, p.sub) if (existing) { log(`identity ${PROVIDER_ID}:${p.sub} already linked to user ${existing.user_id} — skip`) continue } await userIdentities.link({ userId: p.userId, provider: PROVIDER_ID, subject: p.sub, email: p.email }) log(`linked ${PROVIDER_ID}:${p.sub} → user ${p.userId}`) } log('done.') } main() .catch((err) => { // eslint-disable-next-line no-console console.error('[seed-sso] FAILED', err) process.exitCode = 1 }) .finally(async () => { try { await close() } catch { /* ignore shutdown errors */ } })