okengine 0.4.3 → 0.5.0
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.
- package/package.json +3 -1
- package/site/content/docs/ai/index.mdx +24 -0
- package/site/content/docs/ai/llms-txt.mdx +3 -0
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/console/gates.mdx +46 -8
- package/site/content/docs/console/index.mdx +54 -0
- package/site/content/docs/console/meta.json +1 -0
- package/site/content/docs/elements/gate.mdx +187 -48
- package/site/content/docs/elements/index.mdx +45 -0
- package/site/content/docs/elements/meta.json +1 -1
- package/site/content/docs/get-started/basic-usage.mdx +4 -3
- package/site/content/docs/get-started/index.mdx +33 -0
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/index.mdx +9 -31
- package/site/content/docs/plugins/anonymous.mdx +95 -0
- package/site/content/docs/plugins/compression.mdx +2 -2
- package/site/content/docs/plugins/cors.mdx +2 -2
- package/site/content/docs/plugins/csrf.mdx +2 -2
- package/site/content/docs/plugins/email-otp.mdx +111 -0
- package/site/content/docs/plugins/{security-headers.mdx → headers.mdx} +1 -1
- package/site/content/docs/plugins/index.mdx +69 -0
- package/site/content/docs/plugins/magic-link.mdx +112 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +8 -8
- package/site/content/docs/plugins/meta.json +10 -1
- package/site/content/docs/plugins/passkey.mdx +128 -0
- package/site/content/docs/plugins/phone-number.mdx +111 -0
- package/site/content/docs/plugins/two-factor.mdx +116 -0
- package/site/content/docs/plugins/username.mdx +117 -0
- package/site/content/docs/reference/client.mdx +331 -0
- package/site/content/docs/reference/fx.mdx +20 -5
- package/site/content/docs/reference/index.mdx +45 -0
- package/site/content/docs/reference/meta.json +11 -1
- package/site/content/docs/reference/plugins.mdx +25 -14
- package/src/auth/auth.test.ts +20 -2
- package/src/auth/bindings.ts +439 -0
- package/src/auth/breach-check.ts +112 -0
- package/src/auth/config.ts +288 -0
- package/src/auth/cookies.ts +123 -0
- package/src/auth/gate-auth.test.ts +379 -0
- package/src/auth/identity.ts +190 -0
- package/src/auth/index.ts +117 -1
- package/src/auth/method-context.ts +33 -0
- package/src/auth/operator.ts +27 -1
- package/src/auth/password-policy.test.ts +126 -0
- package/src/auth/password-policy.ts +77 -0
- package/src/auth/plugin.ts +62 -4
- package/src/auth/rate.ts +45 -0
- package/src/auth/schema.ts +260 -0
- package/src/auth/secondary-storage.ts +37 -0
- package/src/auth/sessions.ts +58 -1
- package/src/auth/tables.ts +4 -0
- package/src/auth/verification.ts +78 -0
- package/src/cli/dev.test.ts +3 -3
- package/src/cli/schema.ts +95 -23
- package/src/client/auth.ts +120 -0
- package/src/client-react/index.ts +93 -0
- package/src/compiler/aot.test.ts +2 -1
- package/src/compiler/extract.ts +19 -0
- package/src/compiler/response.ts +16 -2
- package/src/console/server/app.ts +10 -6
- package/src/console/server/auth-rate.test.ts +3 -3
- package/src/console/server/bind.ts +12 -1
- package/src/console/server/channels.test.ts +1 -1
- package/src/console/server/console-gates.ts +14 -0
- package/src/console/server/console.test.ts +6 -6
- package/src/console/server/flows-invoke.test.ts +2 -2
- package/src/console/server/flows.ts +2 -0
- package/src/console/server/gates.ts +8 -1
- package/src/console/server/operator-db.test.ts +4 -4
- package/src/console/server/operator-db.ts +22 -4
- package/src/console/server/security.gate.test.ts +3 -3
- package/src/console/ui/gates/fixture.ts +4 -0
- package/src/console/ui/gates/types.ts +2 -0
- package/src/console/ui/shell/client.ts +1 -0
- package/src/elements/gate/boot.ts +136 -0
- package/src/elements/gate/config.ts +69 -0
- package/src/elements/gate/declare.ts +51 -1
- package/src/elements/gate/runtime.ts +3 -1
- package/src/elements/gate.test.ts +77 -0
- package/src/elements/gate.ts +20 -1
- package/src/elements/index.ts +8 -0
- package/src/index.ts +11 -0
- package/src/kernel/app.ts +253 -32
- package/src/kernel/boot.test.ts +40 -3
- package/src/kernel/boot.ts +8 -0
- package/src/kernel/call.test.ts +46 -2
- package/src/kernel/edge.test.ts +3 -3
- package/src/kernel/flow.test.ts +2 -2
- package/src/kernel/fx.test.ts +1 -0
- package/src/kernel/fx.ts +60 -0
- package/src/kernel/hooks.test.ts +4 -4
- package/src/kernel/index.ts +12 -0
- package/src/kernel/pipeline.test.ts +12 -8
- package/src/kernel/pipeline.ts +23 -4
- package/src/kernel/plugin/decorate.test.ts +3 -3
- package/src/kernel/plugin/scoping.test.ts +3 -3
- package/src/kernel/plugin-elements.test.ts +51 -0
- package/src/kernel/plugin-needs.test.ts +83 -0
- package/src/kernel/plugin-needs.ts +129 -0
- package/src/kernel/plugin.ts +101 -0
- package/src/kernel/registry-isolation.test.ts +5 -5
- package/src/kernel/registry.ts +102 -3
- package/src/manifest/types.ts +2 -0
- package/src/plugins/anonymous.ts +58 -0
- package/src/plugins/auth/shared.ts +121 -0
- package/src/plugins/auth-methods.test.ts +176 -0
- package/src/plugins/compression.test.ts +5 -5
- package/src/plugins/config-source.test.ts +1 -1
- package/src/plugins/cors.test.ts +16 -10
- package/src/plugins/csrf.test.ts +1 -1
- package/src/plugins/email-otp.ts +161 -0
- package/src/plugins/index.ts +31 -0
- package/src/plugins/ip-allowlist.test.ts +19 -9
- package/src/plugins/magic-link.ts +163 -0
- package/src/plugins/maintenance-mode.test.ts +9 -5
- package/src/plugins/passkey.ts +216 -0
- package/src/plugins/phone-number.ts +149 -0
- package/src/plugins/security-headers.test.ts +14 -14
- package/src/plugins/two-factor.ts +249 -0
- package/src/plugins/username.ts +148 -0
- package/src/runs/runs.test.ts +6 -2
- package/src/runtime/primitives.ts +37 -4
- package/src/runtime/serve.test.ts +3 -2
- package/src/runtime/types.ts +25 -2
- package/src/test/create-test-app.test.ts +1 -1
- package/src/test/create-test-app.ts +4 -1
- package/src/test/provisions.integration.test.ts +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Passkey"
|
|
3
|
+
description: "Official plugin — simplified WebAuthn register and authenticate under /auth."
|
|
4
|
+
icon: "FingerprintPattern"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`passkey()` adds register and authenticate Flows for passkey-shaped credentials (`oke_passkeys`).
|
|
9
|
+
v1 is a simplified ceremony — options return a challenge; register/authenticate accept the
|
|
10
|
+
payload your client posts (not a full browser WebAuthn SDK).
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Enable `gate.auth`, then `.plug(passkey())`. Registration needs a Bearer session; authenticate is
|
|
14
|
+
public. Wire `navigator.credentials` (or tests) yourself — the plugin stores and verifies the
|
|
15
|
+
posted fields.
|
|
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());
|
|
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: "...",
|
|
49
|
+
publicKey: "...",
|
|
50
|
+
userId: opts.data!.userId,
|
|
51
|
+
challenge: opts.data!.challenge,
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Paths: `POST /auth/passkey/register/options`, `POST /auth/passkey/register`.
|
|
56
|
+
|
|
57
|
+
</Step>
|
|
58
|
+
|
|
59
|
+
<Step>
|
|
60
|
+
### Authenticate
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
await api.auth.passkeyAuthenticateOptions({});
|
|
64
|
+
const { data } = await api.auth.passkeyAuthenticate({
|
|
65
|
+
credentialId: "...",
|
|
66
|
+
userId: "...",
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Paths: `POST /auth/passkey/authenticate/options`, `POST /auth/passkey/authenticate`.
|
|
71
|
+
v1 issues a session when the stored credential matches `userId`.
|
|
72
|
+
|
|
73
|
+
</Step>
|
|
74
|
+
|
|
75
|
+
</Steps>
|
|
76
|
+
|
|
77
|
+
## Options
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Meaning |
|
|
80
|
+
| ------------ | ------------------- | ------------- | -------------------------------- |
|
|
81
|
+
| `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
|
|
82
|
+
| `sessions` | `SessionStore` | active\* | Session store |
|
|
83
|
+
| `now` | `() => number` | `Date.now` | Injectable clock |
|
|
84
|
+
| `passkeys` | `PasskeyStore` | new | Credential → user mapping |
|
|
85
|
+
| `challenges` | `VerificationStore` | new | Registration / auth challenges |
|
|
86
|
+
| `rpId` | `string` | `"localhost"` | Relying party id in options |
|
|
87
|
+
|
|
88
|
+
## Surfaces
|
|
89
|
+
|
|
90
|
+
| Flow | Path | Gate |
|
|
91
|
+
| --------------------------------- | ----------------------------------------- | ------------------------ |
|
|
92
|
+
| `auth.passkeyRegisterOptions` | `POST /auth/passkey/register/options` | session + bearer |
|
|
93
|
+
| `auth.passkeyRegister` | `POST /auth/passkey/register` | session + bearer |
|
|
94
|
+
| `auth.passkeyAuthenticateOptions` | `POST /auth/passkey/authenticate/options` | `gate.public` + otp rate |
|
|
95
|
+
| `auth.passkeyAuthenticate` | `POST /auth/passkey/authenticate` | `gate.public` + otp rate |
|
|
96
|
+
|
|
97
|
+
**Consequence:** production apps should replace the simplified verify with a standards-compliant
|
|
98
|
+
WebAuthn verifier when you leave local/test.
|
|
99
|
+
|
|
100
|
+
## Troubleshooting
|
|
101
|
+
|
|
102
|
+
<Accordions>
|
|
103
|
+
<Accordion title="register fails with unauthenticated">
|
|
104
|
+
|
|
105
|
+
Sign in with another method first. `userId` in the body must match the Bearer session.
|
|
106
|
+
|
|
107
|
+
</Accordion>
|
|
108
|
+
<Accordion title="authenticate returns invalid_credentials">
|
|
109
|
+
|
|
110
|
+
Unknown `credentialId`, or `userId` does not match the stored credential. Re-run registration
|
|
111
|
+
after a successful session.
|
|
112
|
+
|
|
113
|
+
</Accordion>
|
|
114
|
+
</Accordions>
|
|
115
|
+
|
|
116
|
+
## Learn more
|
|
117
|
+
|
|
118
|
+
- [Two-factor](/docs/plugins/two-factor) — TOTP step-up
|
|
119
|
+
- [Gate](/docs/elements/gate) — `gate.auth`
|
|
120
|
+
- [Client](/docs/reference/client) — calling `/auth` from the browser
|
|
121
|
+
|
|
122
|
+
## Next
|
|
123
|
+
|
|
124
|
+
<Cards>
|
|
125
|
+
<Card title="Two-factor" description="TOTP enable / verify." href="/docs/plugins/two-factor" />
|
|
126
|
+
<Card title="Gate" description="Builtin auth and policies." href="/docs/elements/gate" />
|
|
127
|
+
<Card title="Anonymous" description="Guest sessions." href="/docs/plugins/anonymous" />
|
|
128
|
+
</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,116 @@
|
|
|
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
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Enable `gate.auth`, then `.plug(twoFactor())`. Enable and disable need a Bearer session —
|
|
13
|
+
verify is public and takes `{ userId, code }` after password (or other) sign-in.
|
|
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 { twoFactor } from "okengine/plugins";
|
|
26
|
+
|
|
27
|
+
export const app = oke({
|
|
28
|
+
name: "shop",
|
|
29
|
+
env: "local",
|
|
30
|
+
gate: { auth: {} },
|
|
31
|
+
}).plug(twoFactor());
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
</Step>
|
|
35
|
+
|
|
36
|
+
<Step>
|
|
37
|
+
### Enable (session required)
|
|
38
|
+
|
|
39
|
+
Wire Bearer on `createClient` (`auth.getToken` / `memorySession`) — calls take input only.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const { data } = await api.auth.twoFactorEnable({});
|
|
43
|
+
// data.secret, data.otpauthUrl, data.recoveryCodes
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`POST /auth/two-factor/enable` — writes `oke_two_factor`. Store recovery codes once; they are
|
|
47
|
+
shown only here.
|
|
48
|
+
|
|
49
|
+
</Step>
|
|
50
|
+
|
|
51
|
+
<Step>
|
|
52
|
+
### Verify a code
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const { data } = await api.auth.twoFactorVerify({ userId, code: "123456" });
|
|
56
|
+
// hybrid session tokens — recovery codes also accepted once each
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`POST /auth/two-factor/verify`. Missing / disabled factor or bad code → `AuthFailed` /
|
|
60
|
+
`invalid_credentials`. Disable: `api.auth.twoFactorDisable` (Bearer via `auth.getToken`).
|
|
61
|
+
|
|
62
|
+
</Step>
|
|
63
|
+
|
|
64
|
+
</Steps>
|
|
65
|
+
|
|
66
|
+
## Options
|
|
67
|
+
|
|
68
|
+
| Option | Type | Default | Meaning |
|
|
69
|
+
| ---------- | ---------------- | ---------- | -------------------------------- |
|
|
70
|
+
| `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
|
|
71
|
+
| `sessions` | `SessionStore` | active\* | Session store |
|
|
72
|
+
| `now` | `() => number` | `Date.now` | Injectable clock |
|
|
73
|
+
| `factors` | `TwoFactorStore` | new | Per-user TOTP + recovery hashes |
|
|
74
|
+
| `issuer` | `string` | `"oke"` | Label in `otpauth://` URLs |
|
|
75
|
+
|
|
76
|
+
## Surfaces
|
|
77
|
+
|
|
78
|
+
| Flow | Path | Gate |
|
|
79
|
+
| ----------------------- | ------------------------------- | ------------------------ |
|
|
80
|
+
| `auth.twoFactorEnable` | `POST /auth/two-factor/enable` | session + bearer |
|
|
81
|
+
| `auth.twoFactorVerify` | `POST /auth/two-factor/verify` | `gate.public` + otp rate |
|
|
82
|
+
| `auth.twoFactorDisable` | `POST /auth/two-factor/disable` | session + bearer |
|
|
83
|
+
|
|
84
|
+
**Consequence:** verify issues a full session for `userId` — use it as the step after first-factor
|
|
85
|
+
sign-in when the account has 2FA enabled.
|
|
86
|
+
|
|
87
|
+
## Troubleshooting
|
|
88
|
+
|
|
89
|
+
<Accordions>
|
|
90
|
+
<Accordion title="twoFactorEnable returns AuthFailed unauthenticated">
|
|
91
|
+
|
|
92
|
+
Wire a session into `createClient` (`auth.getToken` / `memorySession`) from email/password
|
|
93
|
+
(or another method) first — enable is gated on Bearer.
|
|
94
|
+
|
|
95
|
+
</Accordion>
|
|
96
|
+
<Accordion title="verify always fails">
|
|
97
|
+
|
|
98
|
+
Code must be six digits. Check clock skew (±1 window), and that you enrolled the `secret` from
|
|
99
|
+
enable. A recovery code works once, then is consumed.
|
|
100
|
+
|
|
101
|
+
</Accordion>
|
|
102
|
+
</Accordions>
|
|
103
|
+
|
|
104
|
+
## Learn more
|
|
105
|
+
|
|
106
|
+
- [Passkey](/docs/plugins/passkey) — WebAuthn-shaped register / authenticate
|
|
107
|
+
- [Gate](/docs/elements/gate) — session + policies
|
|
108
|
+
- [Username](/docs/plugins/username) — first factor to enroll against
|
|
109
|
+
|
|
110
|
+
## Next
|
|
111
|
+
|
|
112
|
+
<Cards>
|
|
113
|
+
<Card title="Passkey" description="WebAuthn register and assert." href="/docs/plugins/passkey" />
|
|
114
|
+
<Card title="Gate" description="Builtin auth and policies." href="/docs/elements/gate" />
|
|
115
|
+
<Card title="Email OTP" description="Passwordless email codes." href="/docs/plugins/email-otp" />
|
|
116
|
+
</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 "auth"">
|
|
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>
|