{"id":51405274,"url":"https://github.com/alyldas/uniauth-express-passwordless","last_synced_at":"2026-07-04T10:31:35.434Z","repository":{"id":360930928,"uuid":"1252276180","full_name":"alyldas/uniauth-express-passwordless","owner":"alyldas","description":"Passwordless-first Express strategy package for UniAuth.","archived":false,"fork":false,"pushed_at":"2026-05-28T13:00:50.000Z","size":106,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T14:11:34.764Z","etag":null,"topics":["auth","authentication","express","nodejs","otp","passwordless","typescript","uniauth"],"latest_commit_sha":null,"homepage":"https://github.com/alyldas/uniauth-express-passwordless#readme","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alyldas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-28T11:08:08.000Z","updated_at":"2026-05-28T12:31:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alyldas/uniauth-express-passwordless","commit_stats":null,"previous_names":["alyldas/uniauth-express-passwordless"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/alyldas/uniauth-express-passwordless","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alyldas%2Funiauth-express-passwordless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alyldas%2Funiauth-express-passwordless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alyldas%2Funiauth-express-passwordless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alyldas%2Funiauth-express-passwordless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alyldas","download_url":"https://codeload.github.com/alyldas/uniauth-express-passwordless/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alyldas%2Funiauth-express-passwordless/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35118970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth","authentication","express","nodejs","otp","passwordless","typescript","uniauth"],"created_at":"2026-07-04T10:31:34.652Z","updated_at":"2026-07-04T10:31:35.424Z","avatar_url":"https://github.com/alyldas.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @alyldas/uniauth-express-passwordless\n\nPasswordless-first Express strategy package for UniAuth.\n\nThis package sits on top of `@alyldas/uniauth-core`, `@alyldas/uniauth-express`, and `express`. It\nprovides focused passwordless sign-in routes without taking ownership of core authentication,\nverification, session, delivery, database, or UI lifecycle.\n\n## Installation\n\nConfigure GitHub Packages for the `@alyldas` scope before installing:\n\n```sh\nnpm config set @alyldas:registry https://npm.pkg.github.com\nnpm install @alyldas/uniauth-express-passwordless @alyldas/uniauth-core @alyldas/uniauth-express express\n```\n\n## Usage\n\nMount the router at the path owned by the application. The package does not mount `/auth` by\nitself.\n\n```ts\nimport express from \"express\";\nimport {\n  createEmailOtpTestOutbox,\n  createUniAuthEmailOtpRouter,\n} from \"@alyldas/uniauth-express-passwordless\";\n\nconst app = express();\nconst isProduction = process.env.NODE_ENV === \"production\";\nconst localEmailOutbox = isProduction ? undefined : createEmailOtpTestOutbox();\n\napp.use(express.json());\napp.use(\n  \"/auth\",\n  createUniAuthEmailOtpRouter({\n    auth: auth.public,\n    sessionExpiresAt: () =\u003e new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),\n    emailOtpDebug: localEmailOutbox\n      ? {\n          exposeCodeInStartResponse: true,\n          resolveCode: ({ email }) =\u003e\n            localEmailOutbox.findLatestCode({ email }),\n          isProduction: () =\u003e isProduction,\n        }\n      : undefined,\n    transport: {\n      cookie: {\n        name: \"session\",\n        options: {\n          httpOnly: true,\n          sameSite: \"lax\",\n          secure: process.env.NODE_ENV === \"production\",\n        },\n      },\n    },\n  }),\n);\n```\n\n`auth` must be the public UniAuth facade from application-owned UniAuth service setup. Configure\nOTP email delivery in that service setup before mounting the router; this package calls the public\nOTP facade and does not expose raw OTP secrets by default or own production sender lifecycle. The\ncore service owns OTP verification, delivery dispatch through its configured sender, identity\nmaterialization, and session lifecycle.\n\nThe local outbox example is only for tests and local smoke flows. Wire `localEmailOutbox.sender` into\nyour UniAuth core email sender setup only outside production. Production delivery remains\napplication-owned through the core `EmailSender` configuration, for example SMTP, SES, Postmark, or\nanother provider client managed by the application.\n\n## Routes\n\n### `POST /email-otp/start`\n\nStarts an email OTP sign-in challenge.\n\nRequest body:\n\n```json\n{\n  \"email\": \"user@example.com\",\n  \"metadata\": {\n    \"requestId\": \"request_123\"\n  }\n}\n```\n\nResponse body:\n\n```json\n{\n  \"verificationId\": \"verification_123\",\n  \"expiresAt\": \"2026-01-01T00:05:00.000Z\",\n  \"delivery\": \"email\"\n}\n```\n\nThe response is neutral for account existence. It does not expose raw OTP secrets, secret hashes, or\ninternal verification fields.\n\nFor tests or local smoke flows, `emailOtpDebug.exposeCodeInStartResponse` can add a debug-only code\nfield:\n\n```json\n{\n  \"verificationId\": \"verification_123\",\n  \"expiresAt\": \"2026-01-01T00:05:00.000Z\",\n  \"delivery\": \"email\",\n  \"debug\": {\n    \"code\": \"123456\"\n  }\n}\n```\n\nThis requires explicit opt-in and an application-provided `resolveCode` function. The router rejects\ndebug code exposure when its production guard reports production. Passing only a resolver is not\nenough; `exposeCodeInStartResponse` must also be `true`.\n\n### `POST /email-otp/sign-in`\n\nFinishes an email OTP sign-in challenge.\n\nRequest body:\n\n```json\n{\n  \"verificationId\": \"verification_123\",\n  \"code\": \"123456\",\n  \"metadata\": {\n    \"requestId\": \"request_123\"\n  }\n}\n```\n\n`secret` may be sent instead of `code`.\n\nThe response contains the safe public auth result returned by the UniAuth public facade. A session\ncookie is written only when `transport.cookie` is configured. In that mode, `sessionToken` is omitted\nfrom the JSON response so browser JavaScript does not receive the bearer token. Without cookie\ntransport, the safe response includes `sessionToken`, and the application owns how to store or\nforward it. Request bodies do not control server time or session expiry; use `sessionExpiresAt` in\nrouter options for application-owned expiry policy. The response does not expose token hashes,\npassword hashes, secret hashes, provider tokens, raw provider payloads, or internal metadata.\n\n## Runtime Boundary\n\nThis package owns only the Express email OTP route surface.\n\nApplication code owns:\n\n- UniAuth service construction\n- email sender runtime configuration\n- production email delivery provider integration\n- session expiration policy\n- SMTP or provider client lifecycle\n- database lifecycle and migrations\n- router mount path\n- deployment-specific cookie options\n\nThis package does not own:\n\n- core verification lifecycle\n- core session lifecycle\n- database persistence\n- SMTP runtime\n- UI\n- roles or permissions\n- admin authorization\n- OAuth, Telegram, or MAX strategies\n\nEmail OTP is the first implemented passwordless runtime scope. Magic-link support is planned for a\nlater change and is not implemented here.\n\n## Public API\n\n```ts\nimport {\n  UNI_AUTH_EXPRESS_PASSWORDLESS_STRATEGY,\n  createEmailOtpTestOutbox,\n  createEmailOtpTestSender,\n  createUniAuthEmailOtpRouter,\n  type UniAuthEmailOtpDebugOptions,\n  type UniAuthEmailOtpSignInFailedEvent,\n  type UniAuthEmailOtpStartedEvent,\n  type UniAuthExpressPasswordlessOptions,\n} from \"@alyldas/uniauth-express-passwordless\";\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falyldas%2Funiauth-express-passwordless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falyldas%2Funiauth-express-passwordless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falyldas%2Funiauth-express-passwordless/lists"}