okengine 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/package.json +3 -1
  2. package/site/content/docs/ai/index.mdx +24 -0
  3. package/site/content/docs/ai/llms-txt.mdx +3 -0
  4. package/site/content/docs/ai/meta.json +1 -1
  5. package/site/content/docs/console/gates.mdx +46 -8
  6. package/site/content/docs/console/index.mdx +54 -0
  7. package/site/content/docs/console/meta.json +1 -0
  8. package/site/content/docs/elements/gate.mdx +187 -48
  9. package/site/content/docs/elements/index.mdx +45 -0
  10. package/site/content/docs/elements/meta.json +1 -1
  11. package/site/content/docs/get-started/basic-usage.mdx +4 -3
  12. package/site/content/docs/get-started/index.mdx +33 -0
  13. package/site/content/docs/get-started/meta.json +1 -1
  14. package/site/content/docs/get-started/why.mdx +10 -10
  15. package/site/content/docs/index.mdx +9 -31
  16. package/site/content/docs/plugins/anonymous.mdx +95 -0
  17. package/site/content/docs/plugins/compression.mdx +2 -2
  18. package/site/content/docs/plugins/cors.mdx +2 -2
  19. package/site/content/docs/plugins/csrf.mdx +2 -2
  20. package/site/content/docs/plugins/email-otp.mdx +111 -0
  21. package/site/content/docs/plugins/{security-headers.mdx → headers.mdx} +11 -11
  22. package/site/content/docs/plugins/index.mdx +69 -0
  23. package/site/content/docs/plugins/magic-link.mdx +112 -0
  24. package/site/content/docs/plugins/maintenance-mode.mdx +8 -8
  25. package/site/content/docs/plugins/meta.json +10 -1
  26. package/site/content/docs/plugins/passkey.mdx +140 -0
  27. package/site/content/docs/plugins/phone-number.mdx +111 -0
  28. package/site/content/docs/plugins/two-factor.mdx +117 -0
  29. package/site/content/docs/plugins/username.mdx +117 -0
  30. package/site/content/docs/reference/client.mdx +331 -0
  31. package/site/content/docs/reference/fx.mdx +20 -5
  32. package/site/content/docs/reference/index.mdx +45 -0
  33. package/site/content/docs/reference/meta.json +11 -1
  34. package/site/content/docs/reference/plugins.mdx +25 -14
  35. package/src/auth/auth.test.ts +20 -2
  36. package/src/auth/bindings.ts +439 -0
  37. package/src/auth/breach-check.ts +112 -0
  38. package/src/auth/config.ts +288 -0
  39. package/src/auth/constant-time.ts +22 -0
  40. package/src/auth/cookies.ts +123 -0
  41. package/src/auth/gate-auth.test.ts +379 -0
  42. package/src/auth/identity.ts +190 -0
  43. package/src/auth/index.ts +119 -1
  44. package/src/auth/method-context.ts +33 -0
  45. package/src/auth/operator.ts +27 -1
  46. package/src/auth/password-policy.test.ts +126 -0
  47. package/src/auth/password-policy.ts +77 -0
  48. package/src/auth/plugin.ts +62 -4
  49. package/src/auth/rate.ts +45 -0
  50. package/src/auth/schema.ts +260 -0
  51. package/src/auth/secondary-storage.ts +37 -0
  52. package/src/auth/sessions.ts +58 -1
  53. package/src/auth/tables.ts +4 -0
  54. package/src/auth/verification.ts +78 -0
  55. package/src/cli/competitor-mention-removal.test.ts +3 -3
  56. package/src/cli/dev.test.ts +3 -3
  57. package/src/cli/schema.ts +95 -23
  58. package/src/client/auth.ts +120 -0
  59. package/src/client-react/index.ts +93 -0
  60. package/src/compiler/aot.test.ts +2 -1
  61. package/src/compiler/extract.ts +19 -0
  62. package/src/compiler/response.ts +16 -2
  63. package/src/console/server/app.ts +10 -6
  64. package/src/console/server/auth-rate.test.ts +3 -3
  65. package/src/console/server/bind.ts +12 -1
  66. package/src/console/server/channels.test.ts +1 -1
  67. package/src/console/server/console-gates.ts +14 -0
  68. package/src/console/server/console.test.ts +6 -6
  69. package/src/console/server/flows-invoke.test.ts +2 -2
  70. package/src/console/server/flows.ts +2 -0
  71. package/src/console/server/gates.ts +8 -1
  72. package/src/console/server/operator-db.test.ts +4 -4
  73. package/src/console/server/operator-db.ts +22 -4
  74. package/src/console/server/security.gate.test.ts +3 -3
  75. package/src/console/ui/gates/fixture.ts +4 -0
  76. package/src/console/ui/gates/types.ts +2 -0
  77. package/src/console/ui/shell/client.ts +1 -0
  78. package/src/elements/gate/boot.ts +136 -0
  79. package/src/elements/gate/config.ts +69 -0
  80. package/src/elements/gate/declare.ts +51 -1
  81. package/src/elements/gate/runtime.ts +3 -1
  82. package/src/elements/gate.test.ts +77 -0
  83. package/src/elements/gate.ts +20 -1
  84. package/src/elements/index.ts +8 -0
  85. package/src/index.ts +11 -0
  86. package/src/kernel/app.ts +253 -32
  87. package/src/kernel/boot.test.ts +40 -3
  88. package/src/kernel/boot.ts +8 -0
  89. package/src/kernel/call.test.ts +46 -2
  90. package/src/kernel/edge.test.ts +3 -3
  91. package/src/kernel/flow.test.ts +2 -2
  92. package/src/kernel/fx.test.ts +1 -0
  93. package/src/kernel/fx.ts +60 -0
  94. package/src/kernel/hooks.test.ts +4 -4
  95. package/src/kernel/index.ts +12 -0
  96. package/src/kernel/pipeline.test.ts +12 -8
  97. package/src/kernel/pipeline.ts +23 -4
  98. package/src/kernel/plugin/decorate.test.ts +3 -3
  99. package/src/kernel/plugin/scoping.test.ts +3 -3
  100. package/src/kernel/plugin-elements.test.ts +51 -0
  101. package/src/kernel/plugin-needs.test.ts +83 -0
  102. package/src/kernel/plugin-needs.ts +129 -0
  103. package/src/kernel/plugin.ts +101 -0
  104. package/src/kernel/registry-isolation.test.ts +5 -5
  105. package/src/kernel/registry.ts +102 -3
  106. package/src/manifest/types.ts +2 -0
  107. package/src/plugins/anonymous.ts +58 -0
  108. package/src/plugins/auth/shared.ts +121 -0
  109. package/src/plugins/auth-methods.security.test.ts +762 -0
  110. package/src/plugins/auth-methods.test.ts +176 -0
  111. package/src/plugins/compression.test.ts +5 -5
  112. package/src/plugins/compression.ts +1 -1
  113. package/src/plugins/config-source.test.ts +12 -12
  114. package/src/plugins/config-source.ts +2 -2
  115. package/src/plugins/cors.test.ts +16 -10
  116. package/src/plugins/cors.ts +1 -1
  117. package/src/plugins/csrf.test.ts +1 -1
  118. package/src/plugins/email-otp.ts +161 -0
  119. package/src/plugins/{security-headers.test.ts → headers.test.ts} +26 -26
  120. package/src/plugins/headers.ts +240 -41
  121. package/src/plugins/index.ts +42 -3
  122. package/src/plugins/ip-allowlist.test.ts +19 -9
  123. package/src/plugins/magic-link.ts +163 -0
  124. package/src/plugins/maintenance-mode.test.ts +9 -5
  125. package/src/plugins/passkey-webauthn.ts +217 -0
  126. package/src/plugins/passkey.ts +282 -0
  127. package/src/plugins/phone-number.ts +149 -0
  128. package/src/plugins/response-headers.ts +54 -0
  129. package/src/plugins/two-factor.ts +253 -0
  130. package/src/plugins/username.ts +148 -0
  131. package/src/runs/runs.test.ts +6 -2
  132. package/src/runtime/primitives.ts +37 -4
  133. package/src/runtime/serve.test.ts +3 -2
  134. package/src/runtime/types.ts +25 -2
  135. package/src/test/create-test-app.test.ts +1 -1
  136. package/src/test/create-test-app.ts +4 -1
  137. package/src/test/provisions.integration.test.ts +1 -1
  138. package/src/plugins/security-headers.ts +0 -255
@@ -0,0 +1,140 @@
1
+ ---
2
+ title: "Passkey"
3
+ description: "Official plugin — WebAuthn register and authenticate under /auth with signature and origin checks."
4
+ icon: "FingerprintPattern"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `passkey()` adds register and authenticate Flows for WebAuthn credentials (`oke_passkeys`).
9
+ Options return a challenge; register and authenticate verify `clientDataJSON` origin + challenge,
10
+ `authenticatorData` rpId hash, and an ECDSA P-256 signature against the stored SPKI public key.
11
+
12
+ <Callout title="The one rule">
13
+ Enable `gate.auth`, then `.plug(passkey())`. Registration needs a Bearer session; authenticate is
14
+ public. Post the full ceremony fields — presence of a stored credential alone never issues a
15
+ session.
16
+ </Callout>
17
+
18
+ ## Quick start
19
+
20
+ <Steps>
21
+
22
+ <Step>
23
+ ### Plug it
24
+
25
+ ```typescript title="src/app.ts"
26
+ import { oke } from "okengine";
27
+ import { passkey } from "okengine/plugins";
28
+
29
+ export const app = oke({
30
+ name: "shop",
31
+ env: "local",
32
+ gate: { auth: {} },
33
+ }).plug(passkey({ origins: ["http://localhost", "https://localhost"] }));
34
+ ```
35
+
36
+ </Step>
37
+
38
+ <Step>
39
+ ### Register (session required)
40
+
41
+ Wire Bearer on `createClient` (`auth.getToken` / `memorySession`) — calls take input only.
42
+
43
+ ```typescript
44
+ const opts = await api.auth.passkeyRegisterOptions({});
45
+ // opts.data: { challenge, rpId, userId }
46
+
47
+ await api.auth.passkeyRegister({
48
+ credentialId: "...", // base64url
49
+ publicKey: "...", // base64url SPKI (ECDSA P-256)
50
+ userId: opts.data!.userId,
51
+ challenge: opts.data!.challenge,
52
+ clientDataJSON: "...", // base64url JSON { type, challenge, origin }
53
+ authenticatorData: "...", // base64url
54
+ signature: "...", // base64url ECDSA over authData || SHA-256(clientDataJSON)
55
+ });
56
+ ```
57
+
58
+ Paths: `POST /auth/passkey/register/options`, `POST /auth/passkey/register`.
59
+
60
+ </Step>
61
+
62
+ <Step>
63
+ ### Authenticate
64
+
65
+ ```typescript
66
+ const opts = await api.auth.passkeyAuthenticateOptions({});
67
+ const { data } = await api.auth.passkeyAuthenticate({
68
+ credentialId: "...",
69
+ challenge: opts.data!.challenge,
70
+ clientDataJSON: "...",
71
+ authenticatorData: "...",
72
+ signature: "...",
73
+ });
74
+ ```
75
+
76
+ Paths: `POST /auth/passkey/authenticate/options`, `POST /auth/passkey/authenticate`.
77
+ Challenges are single-use; wrong origin → `invalid_origin`; bad signature → `invalid_credentials`.
78
+
79
+ </Step>
80
+
81
+ </Steps>
82
+
83
+ ## Options
84
+
85
+ | Option | Type | Default | Meaning |
86
+ | ------------ | ------------------- | ------------------------------------------ | -------------------------------- |
87
+ | `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
88
+ | `sessions` | `SessionStore` | active\* | Session store |
89
+ | `now` | `() => number` | `Date.now` | Injectable clock |
90
+ | `passkeys` | `PasskeyStore` | new | Credential → user mapping |
91
+ | `challenges` | `VerificationStore` | new | Registration / auth challenges |
92
+ | `rpId` | `string` | `"localhost"` | Relying party id |
93
+ | `origins` | `string[]` | `["http://localhost","https://localhost"]` | Allowed `clientDataJSON.origin` |
94
+
95
+ ## Surfaces
96
+
97
+ | Flow | Path | Gate |
98
+ | --------------------------------- | ----------------------------------------- | ------------------------ |
99
+ | `auth.passkeyRegisterOptions` | `POST /auth/passkey/register/options` | session + bearer |
100
+ | `auth.passkeyRegister` | `POST /auth/passkey/register` | session + bearer |
101
+ | `auth.passkeyAuthenticateOptions` | `POST /auth/passkey/authenticate/options` | `gate.public` + otp rate |
102
+ | `auth.passkeyAuthenticate` | `POST /auth/passkey/authenticate` | `gate.public` + otp rate |
103
+
104
+ **Consequence:** a stolen `credentialId` without the private key cannot mint a session. Set
105
+ `origins` to your real app origins before production.
106
+
107
+ ## Troubleshooting
108
+
109
+ <Accordions>
110
+ <Accordion title="register fails with unauthenticated">
111
+
112
+ Sign in with another method first. `userId` in the body must match the Bearer session.
113
+
114
+ </Accordion>
115
+ <Accordion title="authenticate returns invalid_origin">
116
+
117
+ `clientDataJSON.origin` must be in `passkey({ origins })`. Default allows only localhost HTTP/S.
118
+
119
+ </Accordion>
120
+ <Accordion title="authenticate returns invalid_credentials">
121
+
122
+ Unknown `credentialId`, consumed/expired challenge, bad signature, or rpId hash mismatch.
123
+ Re-run authenticate options for a fresh challenge, then sign with the enrolled private key.
124
+
125
+ </Accordion>
126
+ </Accordions>
127
+
128
+ ## Learn more
129
+
130
+ - [Two-factor](/docs/plugins/two-factor) — TOTP step-up
131
+ - [Gate](/docs/elements/gate) — `gate.auth`
132
+ - [Client](/docs/reference/client) — calling `/auth` from the browser
133
+
134
+ ## Next
135
+
136
+ <Cards>
137
+ <Card title="Two-factor" description="TOTP enable / verify." href="/docs/plugins/two-factor" />
138
+ <Card title="Gate" description="Builtin auth and policies." href="/docs/elements/gate" />
139
+ <Card title="Anonymous" description="Guest sessions." href="/docs/plugins/anonymous" />
140
+ </Cards>
@@ -0,0 +1,111 @@
1
+ ---
2
+ title: "Phone number"
3
+ description: "Official plugin — E.164 phone OTP request and verify under /auth."
4
+ icon: "Smartphone"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `phoneNumber()` signs people in with a phone number in E.164 form (`+15551234567`). Codes are
9
+ 6 digits, hashed, 10 minutes, 5 attempts — same security shape as email OTP.
10
+
11
+ <Callout title="The one rule">
12
+ Enable `gate.auth`, then `.plug(phoneNumber())`. Reject non-E.164 numbers. SMS Channel delivery is
13
+ not wired yet — use `exposeDevOtp` locally. Prefer stronger methods when phone is the only factor.
14
+ </Callout>
15
+
16
+ ## Quick start
17
+
18
+ <Steps>
19
+
20
+ <Step>
21
+ ### Plug it
22
+
23
+ ```typescript title="src/app.ts"
24
+ import { oke } from "okengine";
25
+ import { phoneNumber } from "okengine/plugins";
26
+
27
+ export const app = oke({
28
+ name: "shop",
29
+ env: "local",
30
+ gate: { auth: {} },
31
+ }).plug(phoneNumber({ exposeDevOtp: true }));
32
+ ```
33
+
34
+ </Step>
35
+
36
+ <Step>
37
+ ### Request a code
38
+
39
+ ```typescript
40
+ const { data, error } = await api.auth.requestPhoneOtp({ phone: "+15551234567" });
41
+ ```
42
+
43
+ `POST /auth/phone/request`. Invalid E.164 → `AuthFailed` / `invalid_phone`.
44
+
45
+ </Step>
46
+
47
+ <Step>
48
+ ### Verify
49
+
50
+ ```typescript
51
+ const { data } = await api.auth.verifyPhoneOtp({
52
+ phone: "+15551234567",
53
+ otp: data!.devOtp!,
54
+ });
55
+ ```
56
+
57
+ `POST /auth/phone/verify` — first success creates a phone → `userId` mapping.
58
+
59
+ </Step>
60
+
61
+ </Steps>
62
+
63
+ ## Options
64
+
65
+ | Option | Type | Default | Meaning |
66
+ | --------------- | ------------------- | -------- | --------------------------------------- |
67
+ | `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
68
+ | `sessions` | `SessionStore` | active\* | Session store |
69
+ | `ttlMs` | `number` | 10m | Challenge lifetime |
70
+ | `exposeDevOtp` | `boolean` | `false` | Include raw OTP in the request response |
71
+ | `phones` | `PhoneStore` | new | Phone → userId map |
72
+ | `verifications` | `VerificationStore` | new | Challenge store |
73
+
74
+ ## Surfaces
75
+
76
+ | Flow | Path | Gate |
77
+ | ---------------------- | -------------------------- | ------------------------ |
78
+ | `auth.requestPhoneOtp` | `POST /auth/phone/request` | `gate.public` + otp rate |
79
+ | `auth.verifyPhoneOtp` | `POST /auth/phone/verify` | `gate.public` + otp rate |
80
+
81
+ E.164 pattern: `^\+[1-9]\d{7,14}$`.
82
+
83
+ ## Troubleshooting
84
+
85
+ <Accordions>
86
+ <Accordion title="invalid_phone">
87
+
88
+ Number must start with `+` and a non-zero country digit, then 7–14 more digits. No spaces or
89
+ dashes.
90
+
91
+ </Accordion>
92
+ <Accordion title="No SMS arrived">
93
+
94
+ v1 does not send SMS. Use `exposeDevOtp` in local/test, or deliver via your own Channel Flow.
95
+
96
+ </Accordion>
97
+ </Accordions>
98
+
99
+ ## Learn more
100
+
101
+ - [Email OTP](/docs/plugins/email-otp) — email variant of the same OTP rules
102
+ - [Gate](/docs/elements/gate) — `gate.auth`
103
+ - [Channel](/docs/elements/channel) — when you wire SMS yourself
104
+
105
+ ## Next
106
+
107
+ <Cards>
108
+ <Card title="Email OTP" description="Six-digit email codes." href="/docs/plugins/email-otp" />
109
+ <Card title="Two-factor" description="TOTP step-up." href="/docs/plugins/two-factor" />
110
+ <Card title="Gate" description="Builtin auth and policies." href="/docs/elements/gate" />
111
+ </Cards>
@@ -0,0 +1,117 @@
1
+ ---
2
+ title: "Two-factor"
3
+ description: "Official plugin — TOTP enable, verify, and disable for an authenticated session."
4
+ icon: "LockKeyhole"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `twoFactor()` adds RFC 6238 TOTP (HMAC-SHA1, 6 digits, 30s) after someone already has a session.
9
+ Enable returns a secret, `otpauth://` URL, and recovery codes; verify mints a new session.
10
+ TOTP codes are compared in constant time (±1 step window).
11
+
12
+ <Callout title="The one rule">
13
+ Enable `gate.auth`, then `.plug(twoFactor())`. Enable and disable need a Bearer session —
14
+ verify is public and takes `{ userId, code }` after password (or other) sign-in.
15
+ </Callout>
16
+
17
+ ## Quick start
18
+
19
+ <Steps>
20
+
21
+ <Step>
22
+ ### Plug it
23
+
24
+ ```typescript title="src/app.ts"
25
+ import { oke } from "okengine";
26
+ import { twoFactor } from "okengine/plugins";
27
+
28
+ export const app = oke({
29
+ name: "shop",
30
+ env: "local",
31
+ gate: { auth: {} },
32
+ }).plug(twoFactor());
33
+ ```
34
+
35
+ </Step>
36
+
37
+ <Step>
38
+ ### Enable (session required)
39
+
40
+ Wire Bearer on `createClient` (`auth.getToken` / `memorySession`) — calls take input only.
41
+
42
+ ```typescript
43
+ const { data } = await api.auth.twoFactorEnable({});
44
+ // data.secret, data.otpauthUrl, data.recoveryCodes
45
+ ```
46
+
47
+ `POST /auth/two-factor/enable` — writes `oke_two_factor`. Store recovery codes once; they are
48
+ shown only here.
49
+
50
+ </Step>
51
+
52
+ <Step>
53
+ ### Verify a code
54
+
55
+ ```typescript
56
+ const { data } = await api.auth.twoFactorVerify({ userId, code: "123456" });
57
+ // hybrid session tokens — recovery codes also accepted once each
58
+ ```
59
+
60
+ `POST /auth/two-factor/verify`. Missing / disabled factor or bad code → `AuthFailed` /
61
+ `invalid_credentials`. Disable: `api.auth.twoFactorDisable` (Bearer via `auth.getToken`).
62
+
63
+ </Step>
64
+
65
+ </Steps>
66
+
67
+ ## Options
68
+
69
+ | Option | Type | Default | Meaning |
70
+ | ---------- | ---------------- | ---------- | -------------------------------- |
71
+ | `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
72
+ | `sessions` | `SessionStore` | active\* | Session store |
73
+ | `now` | `() => number` | `Date.now` | Injectable clock |
74
+ | `factors` | `TwoFactorStore` | new | Per-user TOTP + recovery hashes |
75
+ | `issuer` | `string` | `"oke"` | Label in `otpauth://` URLs |
76
+
77
+ ## Surfaces
78
+
79
+ | Flow | Path | Gate |
80
+ | ----------------------- | ------------------------------- | ------------------------ |
81
+ | `auth.twoFactorEnable` | `POST /auth/two-factor/enable` | session + bearer |
82
+ | `auth.twoFactorVerify` | `POST /auth/two-factor/verify` | `gate.public` + otp rate |
83
+ | `auth.twoFactorDisable` | `POST /auth/two-factor/disable` | session + bearer |
84
+
85
+ **Consequence:** verify issues a full session for `userId` — use it as the step after first-factor
86
+ sign-in when the account has 2FA enabled.
87
+
88
+ ## Troubleshooting
89
+
90
+ <Accordions>
91
+ <Accordion title="twoFactorEnable returns AuthFailed unauthenticated">
92
+
93
+ Wire a session into `createClient` (`auth.getToken` / `memorySession`) from email/password
94
+ (or another method) first — enable is gated on Bearer.
95
+
96
+ </Accordion>
97
+ <Accordion title="verify always fails">
98
+
99
+ Code must be six digits. Check clock skew (±1 window), and that you enrolled the `secret` from
100
+ enable. A recovery code works once, then is consumed.
101
+
102
+ </Accordion>
103
+ </Accordions>
104
+
105
+ ## Learn more
106
+
107
+ - [Passkey](/docs/plugins/passkey) — WebAuthn register / authenticate
108
+ - [Gate](/docs/elements/gate) — session + policies
109
+ - [Username](/docs/plugins/username) — first factor to enroll against
110
+
111
+ ## Next
112
+
113
+ <Cards>
114
+ <Card title="Passkey" description="WebAuthn register and assert." href="/docs/plugins/passkey" />
115
+ <Card title="Gate" description="Builtin auth and policies." href="/docs/elements/gate" />
116
+ <Card title="Email OTP" description="Passwordless email codes." href="/docs/plugins/email-otp" />
117
+ </Cards>
@@ -0,0 +1,117 @@
1
+ ---
2
+ title: "Username"
3
+ description: "Official plugin — username + password sign-up and sign-in under /auth, plugged onto gate.auth."
4
+ icon: "UserRound"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `username()` lets people register and sign in with a username instead of email. It adds two public
9
+ Flows under `/auth` and contributes the `oke_usernames` table.
10
+
11
+ <Callout title="The one rule">
12
+ Enable `gate.auth` first, then `.plug(username())`. The plugin `.needs("auth")` and joins the HTTP
13
+ router via Bindings — not registry metadata alone.
14
+ </Callout>
15
+
16
+ ## Quick start
17
+
18
+ <Steps>
19
+
20
+ <Step>
21
+ ### Plug it
22
+
23
+ ```typescript title="src/app.ts"
24
+ import { oke } from "okengine";
25
+ import { username } from "okengine/plugins";
26
+
27
+ export const app = oke({
28
+ name: "shop",
29
+ env: "local",
30
+ gate: { auth: {} },
31
+ }).plug(username());
32
+ ```
33
+
34
+ </Step>
35
+
36
+ <Step>
37
+ ### Sign up
38
+
39
+ ```typescript
40
+ const { data, error } = await api.auth.signUpUsername({
41
+ username: "ali",
42
+ password: "CorrectHorse1",
43
+ });
44
+ ```
45
+
46
+ `POST /auth/sign-up/username` — usernames are normalized to lowercase; allowed pattern
47
+ `[a-z0-9._-]{3,64}`.
48
+
49
+ </Step>
50
+
51
+ <Step>
52
+ ### Sign in
53
+
54
+ ```typescript
55
+ const { data } = await api.auth.signInUsername({
56
+ username: "ali",
57
+ password: "CorrectHorse1",
58
+ });
59
+ // data: accessToken, refreshToken, accessExpiresAt, userId
60
+ ```
61
+
62
+ Unknown username and bad password both return `AuthFailed` with
63
+ `reason: "invalid_credentials"` (enumeration-safe).
64
+
65
+ </Step>
66
+
67
+ </Steps>
68
+
69
+ ## Options
70
+
71
+ | Option | Type | Default | Meaning |
72
+ | ----------- | --------------- | ---------- | ----------------------------------------------------------- |
73
+ | `secret` | `string` | active\* | HMAC secret (\*from `gate.auth` when plugged after `oke()`) |
74
+ | `sessions` | `SessionStore` | active\* | Session store shared with Gate auth |
75
+ | `now` | `() => number` | `Date.now` | Injectable clock |
76
+ | `usernames` | `UsernameStore` | new map | Shared in-memory credential store |
77
+
78
+ ## Surfaces
79
+
80
+ | Flow | Path | Gate |
81
+ | --------------------- | ----------------------------- | ---------------------------- |
82
+ | `auth.signUpUsername` | `POST /auth/sign-up/username` | `gate.public` + sign-up rate |
83
+ | `auth.signInUsername` | `POST /auth/sign-in/username` | `gate.public` + sign-in rate |
84
+
85
+ ## Troubleshooting
86
+
87
+ <Accordions>
88
+ <Accordion title="plugin boot failed — needs &quot;auth&quot;">
89
+
90
+ Set `oke({ gate: { auth: { … } } })` before `.plug(username())`.
91
+
92
+ </Accordion>
93
+ <Accordion title="AuthFailed invalid_credentials on sign-up">
94
+
95
+ Username taken, or it fails the `[a-z0-9._-]{3,64}` pattern after lowercasing. Same error shape
96
+ on purpose — do not treat it as "exists" in the UI.
97
+
98
+ </Accordion>
99
+ </Accordions>
100
+
101
+ ## Learn more
102
+
103
+ - [Gate](/docs/elements/gate) — `gate.auth` and posture
104
+ - [Plugins](/docs/plugins) — all auth method plugins
105
+ - [Client](/docs/reference/client) — `createClient` + `memorySession`
106
+
107
+ ## Next
108
+
109
+ <Cards>
110
+ <Card
111
+ title="Anonymous"
112
+ description="Session without a password."
113
+ href="/docs/plugins/anonymous"
114
+ />
115
+ <Card title="Gate" description="Builtin auth and policies." href="/docs/elements/gate" />
116
+ <Card title="Magic link" description="Email link sign-in." href="/docs/plugins/magic-link" />
117
+ </Cards>