@jskit-ai/agent-docs 0.1.251 → 0.1.252

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.
@@ -46,9 +46,17 @@ rejects unauthenticated handshakes before a socket joins any broadcast room.
46
46
  Clients must disconnect and reconnect after their login identity changes so a
47
47
  new handshake establishes the current actor.
48
48
 
49
- When `auth.service.authenticateRequest()` is present, delivery reauthenticates
50
- each socket before sending and rechecks idle sockets every 30 seconds. Revoked,
51
- expired, or changed actors are disconnected. Authentication lookup errors fail
49
+ When `auth.service.authenticateRequest()` is present, each publish reauthenticates
50
+ every socket that could receive it before sending, and every socket is rechecked
51
+ every 30 seconds. A socket could receive a publish when the audience is
52
+ `all_clients` or `broadcast`, when it is already in a target room, or, when an
53
+ audience contributor is registered, when a target room is a user or workspace
54
+ room it could join on revalidation. Other sockets are not checked for that
55
+ publish. Sockets whose authentication requests are identical, such as tabs of
56
+ one browser, share one `authenticateRequest()` call within a publish or a
57
+ 30-second pass; nothing is reused from one publish or pass to the next. Revoked,
58
+ expired, or changed actors are disconnected by the first publish that could
59
+ reach them or the next 30-second check. Authentication lookup errors fail
52
60
  closed.
53
61
 
54
62
  Packages that know which scopes an actor belongs to contribute them through
@@ -131,6 +131,64 @@ or compatibility adapter.
131
131
  the completed password-reset flow before releasing the updated application.
132
132
  This change does not alter identity, session or recovery-token storage.
133
133
 
134
+ ## Google, Microsoft and GitHub sign-in with local auth
135
+
136
+ Local auth signs people in through Google, Microsoft and GitHub when the
137
+ application sets each provider's client id and secret in private Env:
138
+
139
+ | Provider | Env pair | Protocol and scopes |
140
+ | --- | --- | --- |
141
+ | Google | `AUTH_GOOGLE_CLIENT_ID`, `AUTH_GOOGLE_CLIENT_SECRET` | OpenID Connect discovery, `openid email profile` |
142
+ | Microsoft | `AUTH_MICROSOFT_CLIENT_ID`, `AUTH_MICROSOFT_CLIENT_SECRET` | OpenID Connect discovery through the `common` endpoint, `openid email profile` |
143
+ | GitHub | `AUTH_GITHUB_CLIENT_ID`, `AUTH_GITHUB_CLIENT_SECRET` | OAuth 2.0, `read:user user:email`, email from `GET /user/emails` |
144
+
145
+ A provider appears in `oauthLogin.providers` only when both values of its pair
146
+ are set; setting one without the other stops startup. `APP_PUBLIC_URL` is then
147
+ required, and every provider's app registration must allow exactly
148
+ `${APP_PUBLIC_URL}/auth/login` as its redirect address. The login page posts the
149
+ callback to `/api/oauth/complete`, which completes the provider the attempt
150
+ started with.
151
+
152
+ Register the Microsoft app for accounts in any organizational directory and
153
+ personal Microsoft accounts, and add the optional ID token claim `xms_edov`.
154
+
155
+ Security behavior:
156
+
157
+ - The authorization-code flow uses PKCE (`S256`) and `state`, plus `nonce` for
158
+ OpenID Connect. The attempt is stored server-side, in the file store or the
159
+ `auth_local_oauth_attempts` table, under the hash of a random httpOnly
160
+ `jskit_local_oauth_attempt` cookie (`__Host-` in production), so it completes
161
+ only in the starting browser and works across several servers. It is deleted
162
+ when used and expires after ten minutes.
163
+ - ID tokens are verified against the provider's JWKS: signature, issuer,
164
+ audience, expiry and nonce.
165
+ - Only a verified email signs in: Google's `email_verified`, GitHub's primary
166
+ verified address, and for Microsoft a personal account or a work or school
167
+ account whose token carries `xms_edov: true`. A Microsoft token without an
168
+ email claim is refused.
169
+ - `returnTo` must be on the application's own origin; anything else returns to
170
+ `/`.
171
+
172
+ A sign-in matches the provider subject first. Otherwise a verified email that
173
+ matches an existing user signs that user in and links the provider identity,
174
+ and a new email creates the user the way `register` does: the
175
+ `auth.local.user.registered` hook runs and profile synchronization provisions
176
+ the personal workspace. An account created this way has no password until
177
+ password recovery sets one. Sessions are ordinary local sessions; provider
178
+ tokens are discarded after sign-in.
179
+
180
+ With providers configured, the `providerLinking` capability is on. The security
181
+ status lists each provider as an `oauth:<provider>` method, where `canEnable`
182
+ means it can be linked and `canDisable` that it can be unlinked.
183
+ `GET /api/settings/security/oauth/:provider/start` links a provider to the
184
+ signed-in account and `DELETE /api/settings/security/oauth/:provider` unlinks
185
+ it. Unlinking the last sign-in method is refused, and so is linking an identity
186
+ that already belongs to another account. The `users/account-settings` pattern's
187
+ sign-in methods section offers both.
188
+
189
+ With `@jskit-ai/auth-provider-local-db-core`, run `npm run db:migrate` to create
190
+ the `auth_local_identities` and `auth_local_oauth_attempts` tables.
191
+
134
192
  ## Browser boundary
135
193
 
136
194
  `@jskit-ai/auth-web` owns the normal auth views, guard runtime, sign-out flow,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.251",
3
+ "version": "0.1.252",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@jskit-ai/config-eslint": "0.1.241",
51
- "@jskit-ai/jskit-catalog": "0.2.3",
51
+ "@jskit-ai/jskit-catalog": "0.2.4",
52
52
  "@playwright/test": "1.61.1",
53
53
  "@vitejs/plugin-vue": "^6.0.7",
54
54
  "eslint": "^10.8.0",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jskit-ai/config-eslint": "0.1.241",
53
- "@jskit-ai/jskit-catalog": "0.2.3",
53
+ "@jskit-ai/jskit-catalog": "0.2.4",
54
54
  "@playwright/test": "1.61.1",
55
55
  "@vitejs/plugin-vue": "^6.0.7",
56
56
  "eslint": "^10.8.0",
@@ -15,7 +15,7 @@ A concrete Fastify, Vue, and JSKIT application foundation for products that do n
15
15
 
16
16
  - Id: `app/minimal-foundation`
17
17
  - Keywords: `app`, `fastify`, `foundation`, `minimal`, `server`, `vite`, `vue`
18
- - Owner: `@jskit-ai/agent-docs@0.1.251`
18
+ - Owner: `@jskit-ai/agent-docs@0.1.252`
19
19
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md)
20
20
  - Examples: [example/](../../skills/jskit/references/patterns/app/minimal-foundation/example/)
21
21
  - Requires: `@jskit-ai/http-runtime`, `@jskit-ai/kernel`
@@ -26,7 +26,7 @@ A concrete JSKIT web application foundation with responsive shell navigation, se
26
26
 
27
27
  - Id: `app/shell-foundation`
28
28
  - Keywords: `app`, `foundation`, `material`, `navigation`, `placements`, `shell`, `vite`, `vue`
29
- - Owner: `@jskit-ai/agent-docs@0.1.251`
29
+ - Owner: `@jskit-ai/agent-docs@0.1.252`
30
30
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/app/shell-foundation/PATTERN.md)
31
31
  - Examples: [example/](../../skills/jskit/references/patterns/app/shell-foundation/example/)
32
32
  - Requires: `@jskit-ai/http-runtime`, `@jskit-ai/kernel`, `@jskit-ai/shell-web`
@@ -37,7 +37,7 @@ Configure an assistant runtime for one application surface and expose its chat a
37
37
 
38
38
  - Id: `assistant/assistant-surface`
39
39
  - Keywords: `ai`, `assistant`, `chat`, `config`, `environment`, `page`, `placement`, `settings`, `surface`
40
- - Owner: `@jskit-ai/assistant-runtime@0.2.3`
40
+ - Owner: `@jskit-ai/assistant-runtime@0.2.4`
41
41
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md)
42
42
  - Examples: [example/](../../skills/jskit/references/patterns/assistant/assistant-surface/example/)
43
43
  - Requires: `@jskit-ai/assistant-runtime`, `@jskit-ai/connectors-catalog`, `@jskit-ai/connectors-core`, `@jskit-ai/shell-web`
@@ -48,7 +48,7 @@ Run one OpenID Connect sign-in for all of an owner's apps as its own JSKIT proje
48
48
 
49
49
  - Id: `auth/accounts-hub`
50
50
  - Keywords: `accounts hub`, `auth`, `authentication`, `issuer`, `master login`, `oidc`, `openid connect`, `single sign-on`, `sso`
51
- - Owner: `@jskit-ai/oidc-issuer-core@0.1.6`
51
+ - Owner: `@jskit-ai/oidc-issuer-core@0.1.7`
52
52
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/auth/accounts-hub/PATTERN.md)
53
53
  - Examples: [example/](../../skills/jskit/references/patterns/auth/accounts-hub/example/)
54
54
  - Requires: `@jskit-ai/oidc-issuer-core`
@@ -70,7 +70,7 @@ Let people sign in with the accounts they already have in an accounts hub or ano
70
70
 
71
71
  - Id: `auth/oidc-sign-in`
72
72
  - Keywords: `accounts hub`, `auth`, `authentication`, `back-channel logout`, `dynamic client registration`, `issuer`, `master login`, `oidc`, `openid connect`, `single sign-on`, `sso`
73
- - Owner: `@jskit-ai/auth-provider-oidc-core@0.2.1`
73
+ - Owner: `@jskit-ai/auth-provider-oidc-core@0.2.2`
74
74
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/auth/oidc-sign-in/PATTERN.md)
75
75
  - Examples: [example/](../../skills/jskit/references/patterns/auth/oidc-sign-in/example/)
76
76
  - Requires: `@jskit-ai/auth-provider-oidc-core`, `@jskit-ai/auth-web`
@@ -246,7 +246,7 @@ Add a protected administration surface, settings shell, and semantic navigation
246
246
 
247
247
  - Id: `console/console-surface`
248
248
  - Keywords: `admin`, `console`, `navigation`, `owner`, `placement`, `settings`, `surface`
249
- - Owner: `@jskit-ai/console-web@0.1.221`
249
+ - Owner: `@jskit-ai/console-web@0.1.222`
250
250
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/console/console-surface/PATTERN.md)
251
251
  - Examples: [example/](../../skills/jskit/references/patterns/console/console-surface/example/)
252
252
  - Requires: `@jskit-ai/console-core`, `@jskit-ai/console-web`, `@jskit-ai/shell-web`
@@ -257,7 +257,7 @@ Build a routed CRUD user interface as thin application pages over JSKIT's shared
257
257
 
258
258
  - Id: `crud/crud-screen-set`
259
259
  - Keywords: `actions`, `add`, `crud`, `delete`, `edit`, `filters`, `list`, `material`, `routes`, `view`, `vue`
260
- - Owner: `@jskit-ai/http-web@0.2.2`
260
+ - Owner: `@jskit-ai/http-web@0.2.3`
261
261
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md)
262
262
  - Examples: [example/](../../skills/jskit/references/patterns/crud/crud-screen-set/example/)
263
263
  - Requires: `@jskit-ai/http-web`, `@jskit-ai/resource-crud-core`
@@ -323,7 +323,7 @@ Add JSKIT realtime events with an optional Redis backplane and an explicit shell
323
323
 
324
324
  - Id: `realtime/realtime-application`
325
325
  - Keywords: `realtime`, `redis`, `socket.io`, `sockets`, `status`, `websocket`
326
- - Owner: `@jskit-ai/realtime@0.2.1`
326
+ - Owner: `@jskit-ai/realtime@0.2.2`
327
327
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md)
328
328
  - Examples: [example/](../../skills/jskit/references/patterns/realtime/realtime-application/example/)
329
329
  - Requires: `@jskit-ai/realtime`, `@jskit-ai/shell-web`
@@ -334,7 +334,7 @@ Compose application-owned Google Publisher Tag delivery with the generic rewarde
334
334
 
335
335
  - Id: `rewards/google-rewarded`
336
336
  - Keywords: `ads`, `google`, `publisher tag`, `rewarded`, `unlock`
337
- - Owner: `@jskit-ai/rewarded-web@0.2.1`
337
+ - Owner: `@jskit-ai/rewarded-web@0.2.2`
338
338
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md)
339
339
  - Examples: [example/](../../skills/jskit/references/patterns/rewards/google-rewarded/example/)
340
340
  - Requires: `@jskit-ai/rewarded-web`
@@ -345,7 +345,7 @@ Define a server feature through explicit capabilities and first-class actions, a
345
345
 
346
346
  - Id: `server/feature-package`
347
347
  - Keywords: `actions`, `feature`, `json-rest`, `knex`, `orchestration`, `package`, `provider`, `repository`, `routes`, `server`
348
- - Owner: `@jskit-ai/agent-docs@0.1.251`
348
+ - Owner: `@jskit-ai/agent-docs@0.1.252`
349
349
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/server/feature-package/PATTERN.md)
350
350
  - Examples: [example/](../../skills/jskit/references/patterns/server/feature-package/example/)
351
351
  - Requires: `@jskit-ai/kernel`
@@ -374,11 +374,11 @@ Add product routes and shell extensions through file routing, semantic placement
374
374
 
375
375
  ## Account settings surface
376
376
 
377
- Compose an account settings route and profile, preference, and notification sections from JSKIT user web components.
377
+ Compose an account settings route and profile, preference, notification, and sign-in method sections from JSKIT user web components.
378
378
 
379
379
  - Id: `users/account-settings`
380
- - Keywords: `account`, `notifications`, `preferences`, `profile`, `settings`, `user`, `vue`
381
- - Owner: `@jskit-ai/users-web@0.2.2`
380
+ - Keywords: `account`, `link provider`, `notifications`, `oauth`, `preferences`, `profile`, `settings`, `sign-in methods`, `user`, `vue`
381
+ - Owner: `@jskit-ai/users-web@0.2.3`
382
382
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/users/account-settings/PATTERN.md)
383
383
  - Examples: [example/](../../skills/jskit/references/patterns/users/account-settings/example/)
384
384
  - Requires: `@jskit-ai/shell-web`, `@jskit-ai/users-core`, `@jskit-ai/users-web`
@@ -389,7 +389,7 @@ Expose user administration and workspace-scoped member operations through app-ow
389
389
 
390
390
  - Id: `users/user-administration-server`
391
391
  - Keywords: `account`, `admin`, `member`, `repository`, `resource`, `routes`, `service`, `user`, `workspace`
392
- - Owner: `@jskit-ai/users-core@0.2.1`
392
+ - Owner: `@jskit-ai/users-core@0.2.2`
393
393
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/users/user-administration-server/PATTERN.md)
394
394
  - Examples: [example/](../../skills/jskit/references/patterns/users/user-administration-server/example/)
395
395
  - Requires: `@jskit-ai/crud-core`, `@jskit-ai/users-core`
@@ -400,7 +400,7 @@ Configure roles, workspace access policy, invitations, and app-owned invitation
400
400
 
401
401
  - Id: `workspaces/workspace-server`
402
402
  - Keywords: `access`, `invite`, `membership`, `multitenancy`, `policy`, `role`, `tenancy`, `workspace`
403
- - Owner: `@jskit-ai/workspaces-core@0.2.1`
403
+ - Owner: `@jskit-ai/workspaces-core@0.2.2`
404
404
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md)
405
405
  - Examples: [example/](../../skills/jskit/references/patterns/workspaces/workspace-server/example/)
406
406
  - Requires: `@jskit-ai/users-core`, `@jskit-ai/workspaces-core`
@@ -411,7 +411,7 @@ Compose workspace selection, invitation, member administration, settings, and re
411
411
 
412
412
  - Id: `workspaces/workspace-surfaces`
413
413
  - Keywords: `admin`, `invite`, `member`, `navigation`, `settings`, `surface`, `switcher`, `workspace`
414
- - Owner: `@jskit-ai/workspaces-web@0.2.2`
414
+ - Owner: `@jskit-ai/workspaces-web@0.2.3`
415
415
  - Read: [PATTERN.md](../../skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md)
416
416
  - Examples: [example/](../../skills/jskit/references/patterns/workspaces/workspace-surfaces/example/)
417
417
  - Requires: `@jskit-ai/shell-web`, `@jskit-ai/workspaces-core`, `@jskit-ai/workspaces-web`
@@ -28,6 +28,10 @@ Local functions
28
28
  - `serializeSessions(sessions)`
29
29
  - `parseRecovery(content)`
30
30
  - `serializeRecovery(records)`
31
+ - `parseIdentities(content)`
32
+ - `serializeIdentities(identities)`
33
+ - `parseOAuthAttempts(content)`
34
+ - `serializeOAuthAttempts(attempts)`
31
35
  - `readText(filePath)`
32
36
  - `writeAtomic(filePath, content)`
33
37
  - `encodeJournalContent(content)`
@@ -53,6 +57,15 @@ Exports
53
57
  - `normalizePasswordStrategy`
54
58
  - `verifyPassword`
55
59
 
60
+ ### `src/server/lib/oauth.js`
61
+ Exports
62
+ - `createOAuthClients({ logger = null } = {})`
63
+ - `resolveOAuthProviders(env = {})`
64
+ Local functions
65
+ - `signInError(statusCode, message, { code, provider = null })`
66
+ - `unverifiedSignIn()`
67
+ - `displayNameFrom(source = {})`
68
+
56
69
  ### `src/server/lib/passwords.js`
57
70
  Exports
58
71
  - `hashPassword(password)`
@@ -83,6 +96,7 @@ Local functions
83
96
  - `isDevAuthSession(session)`
84
97
  - `isAuthenticatingSession(session)`
85
98
  - `normalizeDisplayName(value, email)`
99
+ - `hasPassword(user)`
86
100
  - `createId(prefix)`
87
101
  - `safeRequestCookies(request)`
88
102
  - `unauthenticatedAuthResult(clearSession = false)`
@@ -120,4 +134,19 @@ Exports
120
134
  Local functions
121
135
  - `resolveStoreDir(env, appRoot)`
122
136
  - `resolveSessionSecret(env, { storeDir, isProduction })`
123
- - `resolveAppPublicUrl(env, { recoverySender })`
137
+ - `resolveAppPublicUrl(env, { recoverySender, oauthProviders })`
138
+
139
+ ### test-support
140
+
141
+ ### `test-support/oauthHarness.js`
142
+ Exports
143
+ - `APP_URL`
144
+ - `ATTEMPT_COOKIE`
145
+ - `approveAtProvider(service, { provider = "google", returnTo = "/home", cookies = {}, link = false } = {})`
146
+ - `createService({ backend, oauthProviders, profileProjector = null })`
147
+ - `oidcProviderEntry(issuer, { id = "google", label = "Google" } = {})`
148
+ - `sessionCookies(session)`
149
+ - `startFakeGitHub({ user = { id: 4242, login: "ada", name: "Ada Lovelace" }, emails } = {})`
150
+ - `startOidcProvider({ account } = {})`
151
+ Local functions
152
+ - `readBody(req)`
@@ -29,6 +29,8 @@ Local functions
29
29
  - `mapUser(row)`
30
30
  - `mapSession(row)`
31
31
  - `mapRecovery(row)`
32
+ - `mapIdentity(row)`
33
+ - `mapOAuthAttempt(row)`
32
34
  - `userInsert(input = {})`
33
35
  - `sessionInsert(input = {})`
34
36
  - `recoveryInsert(input = {})`
@@ -48,3 +50,7 @@ Exports
48
50
  ### `migrations/auth_local_db_initial.cjs`
49
51
  Exports
50
52
  - None
53
+
54
+ ### `migrations/auth_local_db_oauth.cjs`
55
+ Exports
56
+ - None
@@ -57,7 +57,7 @@ Exports
57
57
  - `createAudienceContributors()`
58
58
  - `createSocketRequest(socket)`
59
59
  - `realtimeAuthenticationRequired(authService = null)`
60
- - `revalidateSocket({ socket, authService, audiences = null, request })`
60
+ - `revalidateSocket({ socket, authService, audiences = null, request, authenticateRequest })`
61
61
  - `registerSocketAudienceBootstrap({ io, logger, authService = null, audiences = null })`
62
62
  - `resolveAudienceTargets(audience, event, { database = null, logger })`
63
63
  Local functions
@@ -68,11 +68,12 @@ Local functions
68
68
  - `resolveScopeWorkspaceId(scope = {})`
69
69
  - `resolveScopeUserId(scope = {})`
70
70
  - `roomForScope(scope = {})`
71
+ - `isScopeRoom(room)`
71
72
  - `applyAudiencePreset(preset, { event, rooms, flags, logger })`
72
73
  - `addAudienceRooms(selection, state)`
73
74
  - `collectUserIds(rows)`
74
75
  - `parseCookieHeader(value = "")`
75
- - `resolveSocketActor(authService, socket, request = createSocketRequest(socket))`
76
+ - `resolveSocketActor(authService, socket, request = createSocketRequest(socket), authenticateRequest = (value) => authService.authenticateRequest(value))`
76
77
  - `authenticationRequiredError()`
77
78
  - `rememberSocketActorId(socket, actorId)`
78
79
  - `registerRequiredSocketAuthentication({ io, logger, authService })`
@@ -83,6 +84,7 @@ Exports
83
84
  - `createRealtimeDelivery({ io, database = null, logger, authService = null, audiences = null })`
84
85
  Local functions
85
86
  - `publicRealtimePayload(event)`
87
+ - `createSharedAuthentication(authService)`
86
88
 
87
89
  ### `src/server/RealtimeProvider.js`
88
90
  Exports
@@ -143,6 +143,10 @@ Exports
143
143
  Exports
144
144
  - None
145
145
 
146
+ ### `patterns/account-settings/example/src/components/account/settings/AccountSettingsSignInMethodsSection.vue`
147
+ Exports
148
+ - None
149
+
146
150
  ### `patterns/account-settings/example/src/pages/account/index.vue`
147
151
  Exports
148
152
  - None
@@ -15,7 +15,7 @@ A concrete Fastify, Vue, and JSKIT application foundation for products that do n
15
15
 
16
16
  - Id: `app/minimal-foundation`
17
17
  - Keywords: `app`, `fastify`, `foundation`, `minimal`, `server`, `vite`, `vue`
18
- - Owner: `@jskit-ai/agent-docs@0.1.251`
18
+ - Owner: `@jskit-ai/agent-docs@0.1.252`
19
19
  - Read: [PATTERN.md](patterns/app/minimal-foundation/PATTERN.md)
20
20
  - Examples: [example/](patterns/app/minimal-foundation/example/)
21
21
  - Requires: `@jskit-ai/http-runtime`, `@jskit-ai/kernel`
@@ -26,7 +26,7 @@ A concrete JSKIT web application foundation with responsive shell navigation, se
26
26
 
27
27
  - Id: `app/shell-foundation`
28
28
  - Keywords: `app`, `foundation`, `material`, `navigation`, `placements`, `shell`, `vite`, `vue`
29
- - Owner: `@jskit-ai/agent-docs@0.1.251`
29
+ - Owner: `@jskit-ai/agent-docs@0.1.252`
30
30
  - Read: [PATTERN.md](patterns/app/shell-foundation/PATTERN.md)
31
31
  - Examples: [example/](patterns/app/shell-foundation/example/)
32
32
  - Requires: `@jskit-ai/http-runtime`, `@jskit-ai/kernel`, `@jskit-ai/shell-web`
@@ -37,7 +37,7 @@ Configure an assistant runtime for one application surface and expose its chat a
37
37
 
38
38
  - Id: `assistant/assistant-surface`
39
39
  - Keywords: `ai`, `assistant`, `chat`, `config`, `environment`, `page`, `placement`, `settings`, `surface`
40
- - Owner: `@jskit-ai/assistant-runtime@0.2.3`
40
+ - Owner: `@jskit-ai/assistant-runtime@0.2.4`
41
41
  - Read: [PATTERN.md](patterns/assistant/assistant-surface/PATTERN.md)
42
42
  - Examples: [example/](patterns/assistant/assistant-surface/example/)
43
43
  - Requires: `@jskit-ai/assistant-runtime`, `@jskit-ai/connectors-catalog`, `@jskit-ai/connectors-core`, `@jskit-ai/shell-web`
@@ -48,7 +48,7 @@ Run one OpenID Connect sign-in for all of an owner's apps as its own JSKIT proje
48
48
 
49
49
  - Id: `auth/accounts-hub`
50
50
  - Keywords: `accounts hub`, `auth`, `authentication`, `issuer`, `master login`, `oidc`, `openid connect`, `single sign-on`, `sso`
51
- - Owner: `@jskit-ai/oidc-issuer-core@0.1.6`
51
+ - Owner: `@jskit-ai/oidc-issuer-core@0.1.7`
52
52
  - Read: [PATTERN.md](patterns/auth/accounts-hub/PATTERN.md)
53
53
  - Examples: [example/](patterns/auth/accounts-hub/example/)
54
54
  - Requires: `@jskit-ai/oidc-issuer-core`
@@ -70,7 +70,7 @@ Let people sign in with the accounts they already have in an accounts hub or ano
70
70
 
71
71
  - Id: `auth/oidc-sign-in`
72
72
  - Keywords: `accounts hub`, `auth`, `authentication`, `back-channel logout`, `dynamic client registration`, `issuer`, `master login`, `oidc`, `openid connect`, `single sign-on`, `sso`
73
- - Owner: `@jskit-ai/auth-provider-oidc-core@0.2.1`
73
+ - Owner: `@jskit-ai/auth-provider-oidc-core@0.2.2`
74
74
  - Read: [PATTERN.md](patterns/auth/oidc-sign-in/PATTERN.md)
75
75
  - Examples: [example/](patterns/auth/oidc-sign-in/example/)
76
76
  - Requires: `@jskit-ai/auth-provider-oidc-core`, `@jskit-ai/auth-web`
@@ -246,7 +246,7 @@ Add a protected administration surface, settings shell, and semantic navigation
246
246
 
247
247
  - Id: `console/console-surface`
248
248
  - Keywords: `admin`, `console`, `navigation`, `owner`, `placement`, `settings`, `surface`
249
- - Owner: `@jskit-ai/console-web@0.1.221`
249
+ - Owner: `@jskit-ai/console-web@0.1.222`
250
250
  - Read: [PATTERN.md](patterns/console/console-surface/PATTERN.md)
251
251
  - Examples: [example/](patterns/console/console-surface/example/)
252
252
  - Requires: `@jskit-ai/console-core`, `@jskit-ai/console-web`, `@jskit-ai/shell-web`
@@ -257,7 +257,7 @@ Build a routed CRUD user interface as thin application pages over JSKIT's shared
257
257
 
258
258
  - Id: `crud/crud-screen-set`
259
259
  - Keywords: `actions`, `add`, `crud`, `delete`, `edit`, `filters`, `list`, `material`, `routes`, `view`, `vue`
260
- - Owner: `@jskit-ai/http-web@0.2.2`
260
+ - Owner: `@jskit-ai/http-web@0.2.3`
261
261
  - Read: [PATTERN.md](patterns/crud/crud-screen-set/PATTERN.md)
262
262
  - Examples: [example/](patterns/crud/crud-screen-set/example/)
263
263
  - Requires: `@jskit-ai/http-web`, `@jskit-ai/resource-crud-core`
@@ -323,7 +323,7 @@ Add JSKIT realtime events with an optional Redis backplane and an explicit shell
323
323
 
324
324
  - Id: `realtime/realtime-application`
325
325
  - Keywords: `realtime`, `redis`, `socket.io`, `sockets`, `status`, `websocket`
326
- - Owner: `@jskit-ai/realtime@0.2.1`
326
+ - Owner: `@jskit-ai/realtime@0.2.2`
327
327
  - Read: [PATTERN.md](patterns/realtime/realtime-application/PATTERN.md)
328
328
  - Examples: [example/](patterns/realtime/realtime-application/example/)
329
329
  - Requires: `@jskit-ai/realtime`, `@jskit-ai/shell-web`
@@ -334,7 +334,7 @@ Compose application-owned Google Publisher Tag delivery with the generic rewarde
334
334
 
335
335
  - Id: `rewards/google-rewarded`
336
336
  - Keywords: `ads`, `google`, `publisher tag`, `rewarded`, `unlock`
337
- - Owner: `@jskit-ai/rewarded-web@0.2.1`
337
+ - Owner: `@jskit-ai/rewarded-web@0.2.2`
338
338
  - Read: [PATTERN.md](patterns/rewards/google-rewarded/PATTERN.md)
339
339
  - Examples: [example/](patterns/rewards/google-rewarded/example/)
340
340
  - Requires: `@jskit-ai/rewarded-web`
@@ -345,7 +345,7 @@ Define a server feature through explicit capabilities and first-class actions, a
345
345
 
346
346
  - Id: `server/feature-package`
347
347
  - Keywords: `actions`, `feature`, `json-rest`, `knex`, `orchestration`, `package`, `provider`, `repository`, `routes`, `server`
348
- - Owner: `@jskit-ai/agent-docs@0.1.251`
348
+ - Owner: `@jskit-ai/agent-docs@0.1.252`
349
349
  - Read: [PATTERN.md](patterns/server/feature-package/PATTERN.md)
350
350
  - Examples: [example/](patterns/server/feature-package/example/)
351
351
  - Requires: `@jskit-ai/kernel`
@@ -374,11 +374,11 @@ Add product routes and shell extensions through file routing, semantic placement
374
374
 
375
375
  ## Account settings surface
376
376
 
377
- Compose an account settings route and profile, preference, and notification sections from JSKIT user web components.
377
+ Compose an account settings route and profile, preference, notification, and sign-in method sections from JSKIT user web components.
378
378
 
379
379
  - Id: `users/account-settings`
380
- - Keywords: `account`, `notifications`, `preferences`, `profile`, `settings`, `user`, `vue`
381
- - Owner: `@jskit-ai/users-web@0.2.2`
380
+ - Keywords: `account`, `link provider`, `notifications`, `oauth`, `preferences`, `profile`, `settings`, `sign-in methods`, `user`, `vue`
381
+ - Owner: `@jskit-ai/users-web@0.2.3`
382
382
  - Read: [PATTERN.md](patterns/users/account-settings/PATTERN.md)
383
383
  - Examples: [example/](patterns/users/account-settings/example/)
384
384
  - Requires: `@jskit-ai/shell-web`, `@jskit-ai/users-core`, `@jskit-ai/users-web`
@@ -389,7 +389,7 @@ Expose user administration and workspace-scoped member operations through app-ow
389
389
 
390
390
  - Id: `users/user-administration-server`
391
391
  - Keywords: `account`, `admin`, `member`, `repository`, `resource`, `routes`, `service`, `user`, `workspace`
392
- - Owner: `@jskit-ai/users-core@0.2.1`
392
+ - Owner: `@jskit-ai/users-core@0.2.2`
393
393
  - Read: [PATTERN.md](patterns/users/user-administration-server/PATTERN.md)
394
394
  - Examples: [example/](patterns/users/user-administration-server/example/)
395
395
  - Requires: `@jskit-ai/crud-core`, `@jskit-ai/users-core`
@@ -400,7 +400,7 @@ Configure roles, workspace access policy, invitations, and app-owned invitation
400
400
 
401
401
  - Id: `workspaces/workspace-server`
402
402
  - Keywords: `access`, `invite`, `membership`, `multitenancy`, `policy`, `role`, `tenancy`, `workspace`
403
- - Owner: `@jskit-ai/workspaces-core@0.2.1`
403
+ - Owner: `@jskit-ai/workspaces-core@0.2.2`
404
404
  - Read: [PATTERN.md](patterns/workspaces/workspace-server/PATTERN.md)
405
405
  - Examples: [example/](patterns/workspaces/workspace-server/example/)
406
406
  - Requires: `@jskit-ai/users-core`, `@jskit-ai/workspaces-core`
@@ -411,7 +411,7 @@ Compose workspace selection, invitation, member administration, settings, and re
411
411
 
412
412
  - Id: `workspaces/workspace-surfaces`
413
413
  - Keywords: `admin`, `invite`, `member`, `navigation`, `settings`, `surface`, `switcher`, `workspace`
414
- - Owner: `@jskit-ai/workspaces-web@0.2.2`
414
+ - Owner: `@jskit-ai/workspaces-web@0.2.3`
415
415
  - Read: [PATTERN.md](patterns/workspaces/workspace-surfaces/PATTERN.md)
416
416
  - Examples: [example/](patterns/workspaces/workspace-surfaces/example/)
417
417
  - Requires: `@jskit-ai/shell-web`, `@jskit-ai/workspaces-core`, `@jskit-ai/workspaces-web`
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@jskit-ai/config-eslint": "0.1.241",
51
- "@jskit-ai/jskit-catalog": "0.2.3",
51
+ "@jskit-ai/jskit-catalog": "0.2.4",
52
52
  "@playwright/test": "1.61.1",
53
53
  "@vitejs/plugin-vue": "^6.0.7",
54
54
  "eslint": "^10.8.0",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jskit-ai/config-eslint": "0.1.241",
53
- "@jskit-ai/jskit-catalog": "0.2.3",
53
+ "@jskit-ai/jskit-catalog": "0.2.4",
54
54
  "@playwright/test": "1.61.1",
55
55
  "@vitejs/plugin-vue": "^6.0.7",
56
56
  "eslint": "^10.8.0",
@@ -3,6 +3,6 @@
3
3
  "type": "module",
4
4
  "dependencies": {
5
5
  "@jskit-ai/database-runtime-mysql": "0.1.248",
6
- "@jskit-ai/oidc-issuer-core": "0.1.6"
6
+ "@jskit-ai/oidc-issuer-core": "0.1.7"
7
7
  }
8
8
  }
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/auth-provider-oidc-core": "0.2.1",
5
+ "@jskit-ai/auth-provider-oidc-core": "0.2.2",
6
6
  "@jskit-ai/auth-web": "0.2.1"
7
7
  }
8
8
  }
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/realtime": "0.2.1",
5
+ "@jskit-ai/realtime": "0.2.2",
6
6
  "@jskit-ai/shell-web": "0.2.1"
7
7
  }
8
8
  }
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  id: users/account-settings
3
3
  title: Account settings surface
4
- summary: Compose an account settings route and profile, preference, and notification sections from JSKIT user web components.
5
- keywords: account, notifications, preferences, profile, settings, user, vue
4
+ summary: Compose an account settings route and profile, preference, notification, and sign-in method sections from JSKIT user web components.
5
+ keywords: account, notifications, preferences, profile, settings, sign-in methods, oauth, link provider, user, vue
6
6
  requires: @jskit-ai/users-core, @jskit-ai/users-web, @jskit-ai/shell-web
7
7
  ---
8
8
 
@@ -38,6 +38,15 @@ app's language; the app does, for example
38
38
  `watch(useAccountLocale(), (code) => { if (code) i18n.global.locale.value = code; }, { immediate: true })`.
39
39
  Section titles may be plain text, a message key or `{ key, fallback }`.
40
40
 
41
+ `runtime.signInMethods.methods` lists the auth provider's OAuth sign-in
42
+ methods as `{ provider, label, linked, canLink, canUnlink }`, read from the
43
+ settings security status. `link(provider)` leaves for the provider through
44
+ `GET /api/settings/security/oauth/:provider/start` and comes back to the current
45
+ page; `unlink(provider)` sends `DELETE /api/settings/security/oauth/:provider`
46
+ and reports the result with the standard toast. `canUnlink` is false for the
47
+ account's only sign-in method. Add this section only when the selected auth
48
+ provider offers provider linking (`providerLinking` in its capabilities).
49
+
41
50
  ## Invariants
42
51
 
43
52
  - Sections use JSKIT account settings APIs rather than direct HTTP helpers.
@@ -51,7 +60,7 @@ Section titles may be plain text, a message key or `{ key, fallback }`.
51
60
 
52
61
  ## Example files
53
62
 
54
- `example/` contains an account route and three concrete settings sections.
63
+ `example/` contains an account route and four concrete settings sections.
55
64
  Adapt their fields and copy while retaining the shared account runtime.
56
65
 
57
66
  ## Variation points
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ runtime: {
4
+ type: Object,
5
+ required: true
6
+ }
7
+ });
8
+
9
+ const signInMethods = props.runtime.signInMethods;
10
+ </script>
11
+
12
+ <template>
13
+ <v-sheet rounded="lg" border class="account-settings-section">
14
+ <v-list lines="two" class="py-0">
15
+ <v-list-item
16
+ v-for="method in signInMethods.methods.value"
17
+ :key="method.provider"
18
+ :title="method.label"
19
+ :subtitle="method.linked ? 'Linked' : 'Not linked'"
20
+ >
21
+ <template #append>
22
+ <v-btn
23
+ v-if="method.linked"
24
+ variant="text"
25
+ color="error"
26
+ :loading="signInMethods.unlinkingProvider.value === method.provider"
27
+ :disabled="!method.canUnlink || signInMethods.isRefreshing.value"
28
+ @click="signInMethods.unlink(method.provider)"
29
+ >
30
+ Unlink
31
+ </v-btn>
32
+ <v-btn
33
+ v-else
34
+ variant="tonal"
35
+ color="primary"
36
+ :disabled="!method.canLink || signInMethods.isRefreshing.value"
37
+ @click="signInMethods.link(method.provider)"
38
+ >
39
+ Link
40
+ </v-btn>
41
+ </template>
42
+ </v-list-item>
43
+ </v-list>
44
+ <p
45
+ v-if="signInMethods.methods.value.some((method) => method.linked && !method.canUnlink)"
46
+ class="account-settings-section__hint text-body-2 text-medium-emphasis mb-0"
47
+ >
48
+ Your only sign-in method can't be unlinked. Set a password or link another provider first.
49
+ </p>
50
+ </v-sheet>
51
+ </template>
52
+
53
+ <style scoped>
54
+ .account-settings-section {
55
+ overflow: hidden;
56
+ }
57
+
58
+ .account-settings-section__hint {
59
+ padding: 0 1rem 1rem;
60
+ }
61
+
62
+ @media (max-width: 640px) {
63
+ .account-settings-section :deep(.v-btn) {
64
+ min-height: 48px;
65
+ }
66
+ }
67
+ </style>
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "@jskit-ai/crud-core": "0.2.1",
13
13
  "@jskit-ai/resource-crud-core": "0.1.192",
14
- "@jskit-ai/workspaces-core": "0.2.1"
14
+ "@jskit-ai/workspaces-core": "0.2.2"
15
15
  },
16
16
  "jskit": {
17
17
  "kind": "runtime",