okengine 0.17.2 → 0.18.3

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 (197) hide show
  1. package/README.md +9 -5
  2. package/manifest.v1.schema.json +61 -2
  3. package/package.json +18 -4
  4. package/site/content/docs/elements/clock.mdx +1 -1
  5. package/site/content/docs/elements/flow.mdx +25 -1
  6. package/site/content/docs/elements/store.mdx +287 -341
  7. package/site/content/docs/elements/vault.mdx +5 -5
  8. package/site/content/docs/get-started/installation.mdx +1 -2
  9. package/site/content/docs/get-started/introduction.mdx +54 -110
  10. package/site/content/docs/get-started/meta.json +9 -1
  11. package/site/content/docs/get-started/testing.mdx +328 -0
  12. package/site/content/docs/get-started/why.mdx +94 -70
  13. package/site/content/docs/index.mdx +1 -1
  14. package/site/content/docs/plugins/apple.mdx +151 -0
  15. package/site/content/docs/plugins/discord.mdx +139 -0
  16. package/site/content/docs/plugins/facebook.mdx +134 -0
  17. package/site/content/docs/plugins/figma.mdx +138 -0
  18. package/site/content/docs/plugins/github.mdx +138 -0
  19. package/site/content/docs/plugins/google.mdx +153 -0
  20. package/site/content/docs/plugins/index.mdx +47 -1
  21. package/site/content/docs/plugins/meta.json +10 -0
  22. package/site/content/docs/plugins/microsoft.mdx +151 -0
  23. package/site/content/docs/plugins/oauth.mdx +188 -0
  24. package/site/content/docs/plugins/x.mdx +125 -0
  25. package/site/content/docs/reference/cli.md +3 -2
  26. package/site/content/docs/reference/client.mdx +58 -1
  27. package/site/content/docs/reference/configuration.mdx +2 -4
  28. package/site/content/docs/reference/fx.mdx +3 -1
  29. package/site/content/docs/reference/index.mdx +0 -5
  30. package/site/content/docs/reference/meta.json +2 -2
  31. package/site/content/docs/reference/okid.mdx +137 -0
  32. package/src/auth/bindings.ts +1 -1
  33. package/src/auth/config.ts +9 -0
  34. package/src/auth/identity-sql.ts +314 -0
  35. package/src/auth/identity.ts +140 -2
  36. package/src/auth/index.ts +17 -1
  37. package/src/auth/method-context.ts +3 -0
  38. package/src/auth/oauth-as/cimd.ts +132 -0
  39. package/src/auth/oauth-as/crypto.test.ts +101 -0
  40. package/src/auth/oauth-as/crypto.ts +393 -0
  41. package/src/auth/oauth-as/errors.ts +68 -0
  42. package/src/auth/oauth-as/http.test.ts +419 -0
  43. package/src/auth/oauth-as/http.ts +842 -0
  44. package/src/auth/oauth-as/stores.ts +61 -0
  45. package/src/auth/oauth-as/tables.ts +142 -0
  46. package/src/auth/tables.ts +0 -11
  47. package/src/bench/README.md +83 -0
  48. package/src/bench/REPORT.md +176 -0
  49. package/src/bench/g01-rls-stamp.bench.ts +194 -0
  50. package/src/bench/g02-clock-per-tenant.bench.ts +158 -0
  51. package/src/bench/g03-signal-once.bench.ts +157 -0
  52. package/src/bench/g03-signal-reconnect.bench.ts +254 -0
  53. package/src/bench/g03-signal-sse-memory.bench.ts +191 -0
  54. package/src/bench/g04-auth-vault-hotpath.bench.ts +170 -0
  55. package/src/bench/g05-sustained-full.bench.ts +265 -0
  56. package/src/bench/g06-mixed-load.bench.ts +260 -0
  57. package/src/bench/g07-vault-crypto.bench.ts +100 -0
  58. package/src/bench/g07-vault-rotate-under-read.bench.ts +285 -0
  59. package/src/bench/g08-conn-oversubscribe.bench.ts +194 -0
  60. package/src/bench/g08-store-kv-durable.bench.ts +133 -0
  61. package/src/bench/g08-store-sql.bench.ts +178 -0
  62. package/src/bench/g09-journal-sustained.bench.ts +203 -0
  63. package/src/bench/g10-observability-contention.bench.ts +246 -0
  64. package/src/bench/g11-cold-start-cycle.bench.ts +164 -0
  65. package/src/bench/g13-elements.bench.ts +427 -0
  66. package/src/bench/g14-graceful-shutdown.bench.ts +244 -0
  67. package/src/bench/g15-postgres-degradation.bench.ts +264 -0
  68. package/src/bench/g16-live-query-fanout.bench.ts +206 -0
  69. package/src/bench/lib/event-loop-lag.ts +26 -0
  70. package/src/bench/lib/infra.ts +60 -0
  71. package/src/bench/lib/report.ts +52 -0
  72. package/src/bench/lib/rss-sampler.ts +61 -0
  73. package/src/bench/lib/signal-pg.ts +88 -0
  74. package/src/bench/load-app.ts +337 -0
  75. package/src/bench/load-child.ts +108 -0
  76. package/src/bench/smoke.bench.ts +43 -0
  77. package/src/cli/competitor-mention-removal.test.ts +28 -0
  78. package/src/cli/doctor-fd.ts +117 -0
  79. package/src/cli/doctor.test.ts +192 -0
  80. package/src/cli/doctor.ts +129 -1
  81. package/src/client/create.ts +95 -1
  82. package/src/client/index.ts +9 -2
  83. package/src/client/transport.ts +11 -4
  84. package/src/client/use-live-query.ts +154 -0
  85. package/src/client-react/index.ts +15 -1
  86. package/src/client-react/live-resource.ts +246 -0
  87. package/src/client-react/use-live-query.test.ts +475 -0
  88. package/src/client-react/use-live-query.ts +530 -0
  89. package/src/compiler/extract.test.ts +518 -0
  90. package/src/compiler/extract.ts +386 -19
  91. package/src/console/server/invoke-user-flow.ts +2 -1
  92. package/src/console/ui-next/dist/assets/{access-page-DnWbnGzq.js → access-page-DY4N6nnk.js} +1 -1
  93. package/src/console/ui-next/dist/assets/{flows-page-BiZ4-6yQ.js → flows-page-CsPDMrVM.js} +1 -1
  94. package/src/console/ui-next/dist/assets/{index-C8NRK2R-.js → index-CcTDXHuz.js} +3 -3
  95. package/src/console/ui-next/dist/assets/{observability-page-CrB6vd1T.js → observability-page-CKR595wP.js} +1 -1
  96. package/src/console/ui-next/dist/assets/{store-page-CS5-aETQ.js → store-page-02xOiqIK.js} +3 -3
  97. package/src/console/ui-next/dist/assets/{units-page-CjtdlW8l.js → units-page-CpPFFKyE.js} +1 -1
  98. package/src/console/ui-next/dist/assets/{vault-page-C6Xxm9SA.js → vault-page-BsMf-9_W.js} +1 -1
  99. package/src/console/ui-next/dist/index.html +1 -1
  100. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +36 -2
  101. package/src/drivers/cdc-outbox.ts +389 -0
  102. package/src/drivers/memory.ts +20 -0
  103. package/src/drivers/oauth-apple.ts +156 -0
  104. package/src/drivers/oauth-discord.ts +79 -0
  105. package/src/drivers/oauth-facebook.ts +80 -0
  106. package/src/drivers/oauth-figma.ts +116 -0
  107. package/src/drivers/oauth-github.ts +92 -0
  108. package/src/drivers/oauth-google.ts +142 -0
  109. package/src/drivers/oauth-microsoft.ts +174 -0
  110. package/src/drivers/oauth-oidc.ts +293 -0
  111. package/src/drivers/oauth-shared.ts +326 -0
  112. package/src/drivers/oauth-types.ts +159 -0
  113. package/src/drivers/oauth-x.ts +77 -0
  114. package/src/drivers/oauth2-common.ts +95 -0
  115. package/src/drivers/oauth2-token.ts +61 -0
  116. package/src/drivers/pg-rls-row-passes.ts +251 -0
  117. package/src/drivers/pg-rls.ts +2 -0
  118. package/src/drivers/postgres.ts +45 -2
  119. package/src/drivers/signal-postgres.ts +2 -1
  120. package/src/elements/channel/runtime.ts +29 -2
  121. package/src/elements/channel.test.ts +52 -0
  122. package/src/elements/gate/boot.ts +29 -2
  123. package/src/elements/store/emit-drizzle.ts +147 -14
  124. package/src/elements/store/field-ddl.test.ts +118 -0
  125. package/src/elements/store/field-types.test.ts +455 -0
  126. package/src/elements/store/list-query.golden.json +777 -0
  127. package/src/elements/store/list-query.parity.test.ts +396 -0
  128. package/src/elements/store/list-query.ts +792 -0
  129. package/src/elements/store/live-default.test.ts +136 -0
  130. package/src/elements/store/live-http.test.ts +160 -0
  131. package/src/elements/store/live-isolation.test.ts +291 -0
  132. package/src/elements/store/live-query-runtime.test.ts +323 -0
  133. package/src/elements/store/live-query-runtime.ts +403 -0
  134. package/src/elements/store/live-query-server.test.ts +377 -0
  135. package/src/elements/store/live-query-server.ts +102 -0
  136. package/src/elements/store/live-query.ts +97 -0
  137. package/src/elements/store/resource.ts +189 -680
  138. package/src/elements/store/rls-row-passes-policies.parity.test.ts +665 -0
  139. package/src/elements/store/schema-decl.ts +539 -41
  140. package/src/elements/store/sql-rls-stamp.test.ts +27 -0
  141. package/src/elements/store/sql-session.ts +297 -35
  142. package/src/elements/store/table.ts +102 -21
  143. package/src/elements/store.test.ts +3 -1
  144. package/src/elements/store.ts +12 -1
  145. package/src/elements/vault/chaos-child.ts +74 -1
  146. package/src/elements/vault/chaos.test.ts +4 -2
  147. package/src/elements/vault/storage.ts +4 -2
  148. package/src/index.ts +4 -1
  149. package/src/kernel/app-auth.ts +1 -0
  150. package/src/kernel/app.ts +116 -2
  151. package/src/kernel/auth-sharing.test.ts +196 -0
  152. package/src/kernel/boot.test.ts +3 -3
  153. package/src/kernel/errors.ts +8 -0
  154. package/src/kernel/fx.test.ts +1 -0
  155. package/src/kernel/fx.ts +14 -2
  156. package/src/kernel/horizontal-child.ts +2 -1
  157. package/src/kernel/http-resource.ts +33 -7
  158. package/src/kernel/identity-host-persist.test.ts +119 -0
  159. package/src/kernel/instance-id.ts +4 -2
  160. package/src/kernel/journal.ts +2 -1
  161. package/src/kernel/mcp-tool.test.ts +95 -0
  162. package/src/kernel/on.ts +9 -0
  163. package/src/kernel/realtime-bind.ts +326 -0
  164. package/src/kernel/resource-live.ts +117 -0
  165. package/src/kernel/triggers.ts +86 -4
  166. package/src/manifest/diff.ts +37 -0
  167. package/src/manifest/types.ts +64 -2
  168. package/src/okid.bench.test.ts +64 -0
  169. package/src/okid.test.ts +338 -0
  170. package/src/okid.ts +245 -0
  171. package/src/plugins/anonymous.ts +19 -1
  172. package/src/plugins/auth/shared.ts +15 -0
  173. package/src/plugins/index.ts +2 -0
  174. package/src/plugins/magic-link.ts +10 -8
  175. package/src/plugins/mcp-oauth.ts +208 -0
  176. package/src/plugins/oauth/flow-store.ts +117 -0
  177. package/src/plugins/oauth/link.ts +69 -0
  178. package/src/plugins/oauth/shared.ts +108 -0
  179. package/src/plugins/oauth/token-vault.ts +100 -0
  180. package/src/plugins/oauth.security.test.ts +535 -0
  181. package/src/plugins/oauth.ts +532 -0
  182. package/src/plugins/otp.ts +48 -6
  183. package/src/plugins/passkey.ts +20 -1
  184. package/src/plugins/two-factor.ts +11 -0
  185. package/src/plugins/username.ts +40 -7
  186. package/src/release/build-lib.ts +7 -1
  187. package/src/release/measure.ts +1 -0
  188. package/src/release/official-plugins.ts +4 -1
  189. package/src/runs/collect.ts +2 -1
  190. package/src/runs/drivers/files.ts +2 -1
  191. package/src/test/create-test-app.ts +114 -5
  192. package/src/test/export-bundle.test.ts +33 -0
  193. package/src/test/live-signals.test.ts +83 -0
  194. package/src/test/tenant-isolation.test.ts +175 -0
  195. package/src/testing.ts +26 -0
  196. package/src/upgrade/codemods.ts +1 -1
  197. package/site/content/docs/reference/migrating-environments.mdx +0 -158
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: "Figma"
3
+ description: "Official plugin — Figma sign-in with HTTP Basic client auth and no email verification signal."
4
+ icon: "PenTool"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Figma is OAuth2 with two twists: the token endpoint uses **HTTP Basic**
9
+ instead of a body field, and the API exposes **no verification flag** — so
10
+ emails are always unverified.
11
+
12
+ <Callout title="The one rule">
13
+ Register your exact callback URI in the Figma app, seed the client secret, and expect unverified
14
+ emails — new accounts only, never takeovers.
15
+ </Callout>
16
+
17
+ ## Quick start
18
+
19
+ <Steps>
20
+
21
+ <Step>
22
+ ### Create an app
23
+
24
+ Figma → Settings → Security → **Personal access tokens / OAuth apps** → create
25
+ an OAuth app. Add `https://app.example.com/auth/oauth/callback/figma` as a
26
+ callback URL.
27
+
28
+ </Step>
29
+
30
+ <Step>
31
+ ### Plug it
32
+
33
+ ```typescript title="src/app.ts"
34
+ import { oke } from "okengine";
35
+ import { oauth } from "okengine/plugins";
36
+
37
+ export const app = oke({
38
+ name: "shop",
39
+ env: "dev",
40
+ gate: { auth: {} },
41
+ }).plug(
42
+ oauth({
43
+ baseUrl: "https://app.example.com",
44
+ providers: {
45
+ figma: { enabled: true },
46
+ },
47
+ }),
48
+ );
49
+ ```
50
+
51
+ </Step>
52
+
53
+ <Step>
54
+ ### Set the client secret
55
+
56
+ ```text
57
+ # .env.local
58
+ OAUTH_FIGMA_CLIENT_SECRET=...
59
+ ```
60
+
61
+ The driver sends it as `Authorization: Basic base64(client_id:client_secret)`
62
+ on the token call.
63
+
64
+ </Step>
65
+
66
+ </Steps>
67
+
68
+ ## How identity works
69
+
70
+ | Aspect | Behavior |
71
+ | -------------- | --------------------------------------------------------------------------------- |
72
+ | Token exchange | `POST https://api.figma.com/v1/oauth/token` with the Basic header + PKCE verifier |
73
+ | Profile | `GET https://api.figma.com/v1/me` |
74
+ | Subject | `id`, falling back to `handle` when absent |
75
+ | Email trust | **always unverified** — no verification field exists |
76
+
77
+ Default scopes: `file_read`. Trim this to what you actually need; sign-in
78
+ itself requires nothing beyond defaults.
79
+
80
+ ## Options
81
+
82
+ | Option | Type | Default | Meaning |
83
+ | --------------------- | ---------- | ------------------ | ------------------------- |
84
+ | `enabled` | `boolean` | `false` | Turn the provider on |
85
+ | `clientId` | `string` | Vault/env\* | `\*OAUTH_FIGMA_CLIENT_ID` |
86
+ | `redirectUri` | `string` | `{baseUrl}…/figma` | Exact registered URI |
87
+ | `scopes` | `string[]` | driver defaults | Extra scopes |
88
+ | `storeProviderTokens` | `boolean` | `false` | Keep tokens in Vault |
89
+
90
+ ## Surfaces
91
+
92
+ | Flow | Path |
93
+ | -------- | ------------------------------------- |
94
+ | Start | `POST /auth/oauth/figma/start` |
95
+ | Callback | `GET+POST /auth/oauth/callback/figma` |
96
+ | Link | `POST /auth/oauth/figma/link` |
97
+
98
+ ## Troubleshooting
99
+
100
+ <Accordions>
101
+ <Accordion title="401 on token exchange">
102
+
103
+ The Basic header is built from _your_ pair — a mismatched id/secret rotation
104
+ or a secret seeded under the wrong provider key. Check
105
+ `OAUTH_FIGMA_CLIENT_SECRET`.
106
+
107
+ </Accordion>
108
+ <Accordion title="invalid_grant">
109
+
110
+ Codes are single-use and expire quickly. Flow rows are too — restart from
111
+ `/start`.
112
+
113
+ </Accordion>
114
+ <Accordion title="Email missing on the profile">
115
+
116
+ Some Figma accounts expose no email. The session signs in without one;
117
+ nothing else changes.
118
+
119
+ </Accordion>
120
+ </Accordions>
121
+
122
+ ## Learn more
123
+
124
+ - [OAuth](/docs/plugins/oauth) — shared flows and security model
125
+ - [GitHub](/docs/plugins/github) — OAuth2 with real verification signal
126
+ - [Vault](/docs/elements/vault) — where secrets live
127
+
128
+ ## Next
129
+
130
+ <Cards>
131
+ <Card title="Google" description="OIDC reference provider." href="/docs/plugins/google" />
132
+ <Card
133
+ title="GitHub"
134
+ description="OAuth2 with verified-email lookup."
135
+ href="/docs/plugins/github"
136
+ />
137
+ <Card title="X" description="PKCE public client." href="/docs/plugins/x" />
138
+ </Cards>
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: "GitHub"
3
+ description: "Official plugin — GitHub sign-in with primary-email lookup from the emails API."
4
+ icon: "GitBranch"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ GitHub is OAuth2 without discovery or ID tokens, so `oauth()` builds the
9
+ identity assertion from two REST calls: `/user` for the account and
10
+ `/user/emails` for the address that actually matters.
11
+
12
+ <Callout title="The one rule">
13
+ Request the `user:email` scope. The public profile email is usually null; the verified primary
14
+ address only exists on the emails endpoint.
15
+ </Callout>
16
+
17
+ ## Quick start
18
+
19
+ <Steps>
20
+
21
+ <Step>
22
+ ### Create an OAuth App
23
+
24
+ GitHub → Settings → Developer settings → OAuth Apps → **New OAuth App**.
25
+ Authorization callback URL:
26
+ `https://app.example.com/auth/oauth/callback/github`.
27
+
28
+ </Step>
29
+
30
+ <Step>
31
+ ### Plug it
32
+
33
+ ```typescript title="src/app.ts"
34
+ import { oke } from "okengine";
35
+ import { oauth } from "okengine/plugins";
36
+
37
+ export const app = oke({
38
+ name: "shop",
39
+ env: "dev",
40
+ gate: { auth: {} },
41
+ }).plug(
42
+ oauth({
43
+ baseUrl: "https://app.example.com",
44
+ providers: {
45
+ github: { enabled: true },
46
+ },
47
+ }),
48
+ );
49
+ ```
50
+
51
+ </Step>
52
+
53
+ <Step>
54
+ ### Set the client secret
55
+
56
+ ```text
57
+ # .env.local
58
+ OAUTH_GITHUB_CLIENT_SECRET=...
59
+ ```
60
+
61
+ </Step>
62
+
63
+ </Steps>
64
+
65
+ ## How identity works
66
+
67
+ | Aspect | Behavior |
68
+ | -------------- | ------------------------------------------------------------------------------ |
69
+ | Token exchange | form POST to `github.com/login/oauth/access_token`, JSON accepted |
70
+ | Profile | `GET https://api.github.com/user` (numeric `id` is the subject) |
71
+ | Email | `GET https://api.github.com/user/emails`; picks the entry with `primary: true` |
72
+ | Email trust | verified only when the selected entry has an explicit `verified: true` |
73
+
74
+ **Consequence:** a GitHub account whose primary email is unverified never
75
+ claims that address during sign-up — the flow provisions without verified
76
+ status instead of risking someone else's inbox.
77
+
78
+ Default scopes: `read:user`, `user:email`. PKCE parameters are sent; GitHub
79
+ ignores them but the protection stays uniform across providers.
80
+
81
+ ## Options
82
+
83
+ | Option | Type | Default | Meaning |
84
+ | --------------------- | ---------- | ------------------- | -------------------------- |
85
+ | `enabled` | `boolean` | `false` | Turn the provider on |
86
+ | `clientId` | `string` | Vault/env\* | `\*OAUTH_GITHUB_CLIENT_ID` |
87
+ | `redirectUri` | `string` | `{baseUrl}…/github` | Exact registered URI |
88
+ | `scopes` | `string[]` | driver defaults | Extra scopes |
89
+ | `storeProviderTokens` | `boolean` | `false` | Keep tokens in Vault |
90
+
91
+ ## Surfaces
92
+
93
+ | Flow | Path |
94
+ | -------- | -------------------------------------- |
95
+ | Start | `POST /auth/oauth/github/start` |
96
+ | Callback | `GET+POST /auth/oauth/callback/github` |
97
+ | Link | `POST /auth/oauth/github/link` |
98
+
99
+ ## Troubleshooting
100
+
101
+ <Accordions>
102
+ <Accordion title="Sign-in succeeds with no email">
103
+
104
+ The token lacks `user:email` (custom scopes dropped it) or the account has no
105
+ verified addresses. The session is valid; add the scope for future flows.
106
+
107
+ </Accordion>
108
+ <Accordion title="401 on /user/emails">
109
+
110
+ App permissions changed or the user revoked the grant under Settings →
111
+ Applications. The driver degrades to no email rather than failing sign-in.
112
+
113
+ </Accordion>
114
+ <Accordion title="redirect_uri mismatch">
115
+
116
+ GitHub compares the registered callback URL exactly. Re-copy the value your
117
+ config produces.
118
+
119
+ </Accordion>
120
+ </Accordions>
121
+
122
+ ## Learn more
123
+
124
+ - [OAuth](/docs/plugins/oauth) — shared flows and security model
125
+ - [Discord](/docs/plugins/discord) — same OAuth2 shape, nullable email
126
+ - [Vault](/docs/elements/vault) — where secrets live
127
+
128
+ ## Next
129
+
130
+ <Cards>
131
+ <Card
132
+ title="Discord"
133
+ description="Verified flag with nullable email."
134
+ href="/docs/plugins/discord"
135
+ />
136
+ <Card title="Google" description="OIDC reference provider." href="/docs/plugins/google" />
137
+ <Card title="X" description="PKCE public client." href="/docs/plugins/x" />
138
+ </Cards>
@@ -0,0 +1,153 @@
1
+ ---
2
+ title: "Google"
3
+ description: "Official plugin — Google sign-in via OIDC discovery and JWKS-verified ID tokens."
4
+ icon: "Globe"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Google is the reference OIDC provider for `oauth()`: discovery, ID-token
9
+ signature checks, and issuer pinning all work the textbook way. If you are
10
+ wiring your first social provider, start here.
11
+
12
+ <Callout title="The one rule">
13
+ Register `https://app.example.com/auth/oauth/callback/google` (your exact origin) as an Authorized
14
+ Redirect URI, then enable it with `providers.google.enabled`. The comparison is byte-exact.
15
+ </Callout>
16
+
17
+ ## Quick start
18
+
19
+ <Steps>
20
+
21
+ <Step>
22
+ ### Create OAuth credentials
23
+
24
+ In Google Cloud Console → APIs & Services → Credentials, create an **OAuth
25
+ client ID** of type _Web application_. Add your callback URI under Authorized
26
+ Redirect URIs.
27
+
28
+ </Step>
29
+
30
+ <Step>
31
+ ### Plug it
32
+
33
+ ```typescript title="src/app.ts"
34
+ import { oke } from "okengine";
35
+ import { oauth } from "okengine/plugins";
36
+
37
+ export const app = oke({
38
+ name: "shop",
39
+ env: "dev",
40
+ gate: { auth: {} },
41
+ }).plug(
42
+ oauth({
43
+ baseUrl: "https://app.example.com",
44
+ providers: {
45
+ google: { enabled: true },
46
+ },
47
+ }),
48
+ );
49
+ ```
50
+
51
+ </Step>
52
+
53
+ <Step>
54
+ ### Set the client secret
55
+
56
+ ```text
57
+ # .env.local
58
+ OAUTH_GOOGLE_CLIENT_SECRET=GOCSPX-...
59
+ ```
60
+
61
+ Boot fails loudly if the secret is missing — the contract is declared by the
62
+ plugin itself.
63
+
64
+ </Step>
65
+
66
+ </Steps>
67
+
68
+ ## How identity works
69
+
70
+ The driver discovers Google's endpoints from
71
+ `https://accounts.google.com/.well-known/openid-configuration` and caches them.
72
+ On callback it verifies the ID token end-to-end:
73
+
74
+ | Check | Rule |
75
+ | --------- | ------------------------------------------------------- |
76
+ | Signature | RS256 / ES256 against Google's published JWKS |
77
+ | Issuer | must equal `https://accounts.google.com` |
78
+ | Audience | must include your client id (`azp` when multi-audience) |
79
+ | Expiry | rejected when stale |
80
+ | Nonce | single-use, bound to the flow row |
81
+
82
+ Email trust follows the OIDC claim: `email_verified: true` marks the address
83
+ verified; anything else — including string `"false"` — stays unverified.
84
+
85
+ Default scopes: `openid`, `email`, `profile`.
86
+
87
+ ## Options
88
+
89
+ | Option | Type | Default | Meaning |
90
+ | --------------------- | ---------- | ------------------- | -------------------------- |
91
+ | `enabled` | `boolean` | `false` | Turn the provider on |
92
+ | `clientId` | `string` | Vault/env\* | `\*OAUTH_GOOGLE_CLIENT_ID` |
93
+ | `redirectUri` | `string` | `{baseUrl}…/google` | Exact registered URI |
94
+ | `scopes` | `string[]` | driver defaults | Extra scopes |
95
+ | `storeProviderTokens` | `boolean` | `false` | Keep tokens in Vault |
96
+
97
+ ## Surfaces
98
+
99
+ | Flow | Path |
100
+ | -------- | -------------------------------------- |
101
+ | Start | `POST /auth/oauth/google/start` |
102
+ | Callback | `GET+POST /auth/oauth/callback/google` |
103
+ | Link | `POST /auth/oauth/google/link` |
104
+
105
+ ## Troubleshooting
106
+
107
+ <Accordions>
108
+ <Accordion title="redirect_uri_mismatch at Google">
109
+
110
+ The registered URI and the sent URI differ. Byte-exact means scheme, host,
111
+ port, path, no trailing slash drift. Copy the value from
112
+ `providers.google.redirectUri` or `baseUrl` synthesis.
113
+
114
+ </Accordion>
115
+ <Accordion title="invalid_client at token exchange">
116
+
117
+ `OAUTH_GOOGLE_CLIENT_SECRET` is missing or stale. Secrets resolve through the
118
+ Vault chain — update `.env.local` or your Vault driver, then restart.
119
+
120
+ </Accordion>
121
+ <Accordion title="aud_mismatch">
122
+
123
+ You are using credentials from a different Google project than the one that
124
+ issued the code, or swapped client ids between environments.
125
+
126
+ </Accordion>
127
+ </Accordions>
128
+
129
+ ## Learn more
130
+
131
+ - [OAuth](/docs/plugins/oauth) — shared flows and security model
132
+ - [Vault](/docs/elements/vault) — where secrets live
133
+ - [Gate](/docs/elements/gate) — `gate.auth`
134
+
135
+ ## Next
136
+
137
+ <Cards>
138
+ <Card
139
+ title="Apple"
140
+ description="form_post + ES256 client-secret JWT."
141
+ href="/docs/plugins/apple"
142
+ />
143
+ <Card
144
+ title="Microsoft"
145
+ description="Entra tenants and issuer templates."
146
+ href="/docs/plugins/microsoft"
147
+ />
148
+ <Card
149
+ title="GitHub"
150
+ description="OAuth2 with verified-email lookup."
151
+ href="/docs/plugins/github"
152
+ />
153
+ </Cards>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Plugins"
3
- description: "Official okengine/plugins extensions — authentication, security, operations, and performance."
3
+ description: "Official okengine/plugins extensions — authentication, OAuth, security, operations, and performance."
4
4
  icon: "Puzzle"
5
5
  source: "docs/spec/unified-theory.md"
6
6
  ---
@@ -22,6 +22,52 @@ First-party plugins you `.plug()` onto an app. Each page is one export from `oke
22
22
  <Card title="Passkey" description="WebAuthn-shaped passkeys." href="/docs/plugins/passkey" />
23
23
  </Cards>
24
24
 
25
+ ## OAuth
26
+
27
+ <Cards>
28
+ <Card
29
+ title="OAuth"
30
+ description="Social sign-in — Authorization Code + PKCE for eight providers."
31
+ href="/docs/plugins/oauth"
32
+ />
33
+ <Card
34
+ title="Apple"
35
+ description="Sign in with Apple — form_post + ES256 JWT."
36
+ href="/docs/plugins/apple"
37
+ />
38
+ <Card
39
+ title="Discord"
40
+ description="Discord OAuth2 with nullable email."
41
+ href="/docs/plugins/discord"
42
+ />
43
+ <Card
44
+ title="Facebook"
45
+ description="Facebook Login — never-verified emails."
46
+ href="/docs/plugins/facebook"
47
+ />
48
+ <Card
49
+ title="Figma"
50
+ description="Figma OAuth2 with HTTP Basic token auth."
51
+ href="/docs/plugins/figma"
52
+ />
53
+ <Card
54
+ title="GitHub"
55
+ description="GitHub OAuth2 with verified-email lookup."
56
+ href="/docs/plugins/github"
57
+ />
58
+ <Card
59
+ title="Google"
60
+ description="Google OIDC — JWKS-verified ID tokens."
61
+ href="/docs/plugins/google"
62
+ />
63
+ <Card
64
+ title="Microsoft"
65
+ description="Entra ID OIDC with tenant-aware issuer checks."
66
+ href="/docs/plugins/microsoft"
67
+ />
68
+ <Card title="X" description="X OAuth2 public client — PKCE only." href="/docs/plugins/x" />
69
+ </Cards>
70
+
25
71
  ## Security
26
72
 
27
73
  <Cards>
@@ -10,6 +10,16 @@
10
10
  "otp",
11
11
  "two-factor",
12
12
  "passkey",
13
+ "---OAuth---",
14
+ "oauth",
15
+ "apple",
16
+ "discord",
17
+ "facebook",
18
+ "figma",
19
+ "github",
20
+ "google",
21
+ "microsoft",
22
+ "x",
13
23
  "---Security---",
14
24
  "headers",
15
25
  "cors",
@@ -0,0 +1,151 @@
1
+ ---
2
+ title: "Microsoft"
3
+ description: "Official plugin — Entra ID sign-in across personal, work, and school accounts with tenant-aware issuer checks."
4
+ icon: "LayoutGrid"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Microsoft (Entra ID) is OIDC with a tenant twist: `common` discovery advertises
9
+ an issuer with the `{tenantid}` placeholder, so the value you pin at start is
10
+ a _template_ validated against the concrete token at callback.
11
+
12
+ <Callout title="The one rule">
13
+ Pick the audience first: `tenant: "common"` for everyone, `organizations` for work/school only,
14
+ `consumers` for personal accounts, or a tenant GUID to lock sign-in to one directory.
15
+ </Callout>
16
+
17
+ ## Quick start
18
+
19
+ <Steps>
20
+
21
+ <Step>
22
+ ### Register an app
23
+
24
+ In the Azure portal → Microsoft Entra ID → App registrations → New
25
+ registration. Choose _Accounts in any organizational directory and personal
26
+ Microsoft accounts_ for `common`. Add your redirect URI under **Web**.
27
+
28
+ </Step>
29
+
30
+ <Step>
31
+ ### Plug it
32
+
33
+ ```typescript title="src/app.ts"
34
+ import { oke } from "okengine";
35
+ import { oauth } from "okengine/plugins";
36
+
37
+ export const app = oke({
38
+ name: "shop",
39
+ env: "dev",
40
+ gate: { auth: {} },
41
+ }).plug(
42
+ oauth({
43
+ baseUrl: "https://app.example.com",
44
+ providers: {
45
+ microsoft: { enabled: true }, // tenant defaults to "common"
46
+ },
47
+ }),
48
+ );
49
+ ```
50
+
51
+ </Step>
52
+
53
+ <Step>
54
+ ### Set the client secret
55
+
56
+ ```text
57
+ # .env.local
58
+ OAUTH_MICROSOFT_CLIENT_SECRET=...
59
+ ```
60
+
61
+ Certificates are not used — the driver authenticates with the shared secret
62
+ form field.
63
+
64
+ </Step>
65
+
66
+ </Steps>
67
+
68
+ ## How identity works
69
+
70
+ | Aspect | Behavior |
71
+ | -------------- | ----------------------------------------------------------------------------------------------------- |
72
+ | Discovery | `{tenant}/oauth2/v2.0/.well-known/openid-configuration`, cached |
73
+ | Issuer pinning | flow stores `https://login.microsoftonline.com/{tenantid}/v2.0`; tokens must match it shape-for-shape |
74
+ | Tenant claim | the token's `tid` must be GUID-shaped and consistent with `iss` |
75
+ | Signature | RS256 against the tenant's published keys |
76
+ | Email trust | OIDC `email_verified` claim |
77
+
78
+ **Consequence:** an ID token minted by a _different_ tenant fails the issuer
79
+ template even though its signature is perfectly valid — the mix-up defense
80
+ survives multi-tenancy.
81
+
82
+ Default scopes: `openid`, `email`, `profile`.
83
+
84
+ ## Options
85
+
86
+ | Option | Type | Default | Meaning |
87
+ | --------------------- | ---------- | ---------------------- | --------------------------------------- |
88
+ | `enabled` | `boolean` | `false` | Turn the provider on |
89
+ | `clientId` | `string` | Vault/env\* | `\*OAUTH_MICROSOFT_CLIENT_ID` |
90
+ | `tenant` | `string` | `"common"` | `organizations`, `consumers`, or a GUID |
91
+ | `redirectUri` | `string` | `{baseUrl}…/microsoft` | Exact registered URI |
92
+ | `scopes` | `string[]` | driver defaults | Extra scopes |
93
+ | `storeProviderTokens` | `boolean` | `false` | Keep tokens in Vault |
94
+
95
+ ## Surfaces
96
+
97
+ | Flow | Path |
98
+ | -------- | ----------------------------------------- |
99
+ | Start | `POST /auth/oauth/microsoft/start` |
100
+ | Callback | `GET+POST /auth/oauth/callback/microsoft` |
101
+ | Link | `POST /auth/oauth/microsoft/link` |
102
+
103
+ ## Troubleshooting
104
+
105
+ <Accordions>
106
+ <Accordion title="issuer_mismatch on every login">
107
+
108
+ Your app registration's _supported account types_ disagree with the configured
109
+ `tenant`. `common` requires multi-tenant + personal accounts; a single-tenant
110
+ GUID needs the matching registration.
111
+
112
+ </Accordion>
113
+ <Accordion title="AADSTS50011 (redirect mismatch)">
114
+
115
+ The reply URL registered in Azure does not byte-match the stored
116
+ `redirectUri`. Re-copy from your plugin config; trailing slashes count.
117
+
118
+ </Accordion>
119
+ <Accordion title="No email arrives">
120
+
121
+ Personal Microsoft accounts can omit email entirely. The flow signs the person
122
+ in without one — exactly like Discord phone-only accounts.
123
+
124
+ </Accordion>
125
+ </Accordions>
126
+
127
+ ## Learn more
128
+
129
+ - [OAuth](/docs/plugins/oauth) — shared flows and security model
130
+ - [Google](/docs/plugins/google) — plain single-issuer OIDC
131
+ - [Vault](/docs/elements/vault) — where secrets live
132
+
133
+ ## Next
134
+
135
+ <Cards>
136
+ <Card
137
+ title="Apple"
138
+ description="form_post + ES256 client-secret JWT."
139
+ href="/docs/plugins/apple"
140
+ />
141
+ <Card
142
+ title="Discord"
143
+ description="Nullable emails, verified flag."
144
+ href="/docs/plugins/discord"
145
+ />
146
+ <Card
147
+ title="GitHub"
148
+ description="OAuth2 with verified-email lookup."
149
+ href="/docs/plugins/github"
150
+ />
151
+ </Cards>