@foir/sdk 0.21.0 → 0.23.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/CHANGELOG.md +135 -1
- package/README.md +15 -15
- package/dist/app/client.d.ts +93 -86
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +14 -13
- package/dist/app/dispatch.d.ts +5 -5
- package/dist/app/errors.d.ts +17 -15
- package/dist/app/index.d.ts +4 -4
- package/dist/app/naming.d.ts +1 -1
- package/dist/app/ticket.d.ts +2 -2
- package/dist/app/vocabulary.generated.d.ts +2 -2
- package/dist/app-contract.js +3 -3
- package/dist/app.js +92 -90
- package/dist/auth/access.d.ts +19 -19
- package/dist/auth/auth.d.ts +251 -117
- package/dist/auth/grant-table.generated.d.ts +5 -4
- package/dist/auth/hosted.d.ts +38 -14
- package/dist/auth/index.d.ts +9 -9
- package/dist/auth/operations.d.ts +153 -33
- package/dist/auth/ops.d.ts +39 -21
- package/dist/auth/session-client.d.ts +3 -3
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +400 -250
- package/dist/client.js +5 -8
- package/dist/codegen/preset.d.ts +1 -1
- package/dist/data/builtins.d.ts +26 -26
- package/dist/data/client.d.ts +3 -3
- package/dist/data/decimal.d.ts +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/manifest.d.ts +1 -1
- package/dist/data/schema.d.ts +2 -2
- package/dist/data/transport.d.ts +14 -13
- package/dist/data/ws-transport.d.ts +1 -1
- package/dist/data.js +8 -11
- package/dist/graphql/content-client.d.ts +13 -13
- package/dist/index.js +1 -4
- package/dist/live/drift.d.ts +1 -1
- package/dist/media/screen-capture.d.ts +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/session.d.ts +10 -10
- package/dist/next.js +78 -60
- package/dist/placement/index.d.ts +13 -13
- package/dist/placement.js +127 -116
- package/dist/push.js +62 -44
- package/dist/react/FoirFeedback.d.ts +2 -2
- package/dist/react/FoirProvider.d.ts +24 -24
- package/dist/react/SignIn.d.ts +4 -4
- package/dist/react/UserButton.d.ts +1 -1
- package/dist/react/feedback-notes.d.ts +8 -6
- package/dist/react/index.d.ts +3 -3
- package/dist/react.js +238 -209
- package/dist/realtime/index.d.ts +1 -1
- package/dist/server/callback-client.d.ts +4 -4
- package/dist/server/events-client.d.ts +21 -21
- package/dist/server/operation-types.d.ts +8 -8
- package/dist/server/require-scoped-token.d.ts +3 -3
- package/dist/server/types.d.ts +2 -2
- package/dist/server/verify-scoped-token.d.ts +3 -3
- package/dist/server.js +130 -126
- package/dist/transport/foir-http.d.ts +14 -14
- package/dist/transport/key-kind.d.ts +1 -12
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -1
package/dist/auth/hosted.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hosted-login bridge — for storefronts that delegate sign-in to a Foir
|
|
3
|
-
* white-label front door (the `
|
|
3
|
+
* white-label front door (the `loginWithFoir` flow) instead of
|
|
4
4
|
* rendering their own login UI.
|
|
5
5
|
*
|
|
6
6
|
* The front door sets a first-party session cookie on the registrable domain;
|
|
7
7
|
* because that domain is shared, the cookie reaches the storefront, which
|
|
8
|
-
* exchanges it server-side for a short-lived
|
|
8
|
+
* exchanges it server-side for a short-lived user bearer + identity. The
|
|
9
9
|
* cookie is the durable credential (re-exchanged on bearer expiry) — there's
|
|
10
10
|
* no separate refresh token in this flow.
|
|
11
11
|
*
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
export interface HostedLoginConfig {
|
|
16
16
|
/** White-label front-door origin, e.g. `https://login.example.com`. */
|
|
17
17
|
loginOrigin: string;
|
|
18
|
-
/** Session cookie name set on the registrable domain. Default `
|
|
18
|
+
/** Session cookie name set on the registrable domain. Default `foir_session`. */
|
|
19
19
|
cookieName?: string;
|
|
20
20
|
/** Session-exchange path on the front door. Default `/session/token`. */
|
|
21
21
|
sessionPath?: string;
|
|
@@ -29,13 +29,13 @@ export interface HostedLoginConfig {
|
|
|
29
29
|
export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string): string;
|
|
30
30
|
/**
|
|
31
31
|
* Build the redirect URL that ENDS the hosted session — the mirror of
|
|
32
|
-
* `hostedLoginUrl`, and the only thing that actually signs a
|
|
32
|
+
* `hostedLoginUrl`, and the only thing that actually signs a user out on
|
|
33
33
|
* this flow.
|
|
34
34
|
*
|
|
35
35
|
* The cookie is the durable credential. Disposing of a bearer, clearing a
|
|
36
|
-
* local session, or calling the `
|
|
36
|
+
* local session, or calling the `logout` mutation (which clears
|
|
37
37
|
* upstream provider tokens and nothing else) all leave it in place, and the
|
|
38
|
-
* next request re-establishes the
|
|
38
|
+
* next request re-establishes the user from it. This URL points at the
|
|
39
39
|
* front door's end-session endpoint, which revokes the session row and clears
|
|
40
40
|
* the cookie before returning the browser to `returnTo`.
|
|
41
41
|
*
|
|
@@ -50,7 +50,7 @@ export declare function hostedLogoutUrl(config: HostedLoginConfig, clientId: str
|
|
|
50
50
|
/** The identity + bearer returned by a successful hosted-session exchange. */
|
|
51
51
|
export interface HostedSession {
|
|
52
52
|
token: string;
|
|
53
|
-
|
|
53
|
+
userId: string;
|
|
54
54
|
email: string;
|
|
55
55
|
/**
|
|
56
56
|
* Seconds until `token` lapses, from the endpoint's `expires_in`.
|
|
@@ -61,7 +61,7 @@ export interface HostedSession {
|
|
|
61
61
|
* lifetime (coupling a storefront to a server constant it cannot see) or to
|
|
62
62
|
* decode `exp` off the JWT. Both re-derive a number the response already
|
|
63
63
|
* carried, and a consumer that does neither treats a 15-minute bearer as good
|
|
64
|
-
* forever — which reads as a
|
|
64
|
+
* forever — which reads as a user being signed out mid-session.
|
|
65
65
|
*
|
|
66
66
|
* Store the death date, not this number: it is seconds from when the exchange
|
|
67
67
|
* ANSWERED, and a value held across requests is already stale.
|
|
@@ -76,9 +76,9 @@ export interface HostedSession {
|
|
|
76
76
|
* reason says what happened.
|
|
77
77
|
*
|
|
78
78
|
* Collapsing all of this to `null` is what made a transient blip on
|
|
79
|
-
* `/session/token` indistinguishable from a genuinely signed-out
|
|
79
|
+
* `/session/token` indistinguishable from a genuinely signed-out user. The
|
|
80
80
|
* only safe-looking action on a bare `null` is to clear the session and send
|
|
81
|
-
* the
|
|
81
|
+
* the user to the front door, so one 502 signed out a user whose 30-day
|
|
82
82
|
* cookie was perfectly valid — and the route's rate limiter is deliberately
|
|
83
83
|
* fail-open for exactly the opposite reason ("a Redis outage should degrade an
|
|
84
84
|
* authenticated app, not log it out").
|
|
@@ -90,7 +90,7 @@ export type HostedSessionResult =
|
|
|
90
90
|
session: HostedSession;
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
* The
|
|
93
|
+
* The user is signed out. Act on it: clear any local session and send
|
|
94
94
|
* them to `hostedLoginUrl`.
|
|
95
95
|
*
|
|
96
96
|
* `no-cookie` — nothing to exchange. `rejected` — the front door refused the
|
|
@@ -102,12 +102,12 @@ export type HostedSessionResult =
|
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* The exchange could not be completed, and it says NOTHING about whether the
|
|
105
|
-
*
|
|
105
|
+
* user is signed in. KEEP the existing session, serve the request, try
|
|
106
106
|
* again on the next one.
|
|
107
107
|
*
|
|
108
108
|
* `forbidden` is the odd one: a 403 means the CSRF header did not arrive
|
|
109
109
|
* (a proxy stripping it, most likely), which is a misconfiguration rather
|
|
110
|
-
* than a blip. Retrying will not fix it — but signing the
|
|
110
|
+
* than a blip. Retrying will not fix it — but signing the user out would
|
|
111
111
|
* be wrong, so the safe action is the same and it is grouped by that.
|
|
112
112
|
*/
|
|
113
113
|
| {
|
|
@@ -119,7 +119,7 @@ export type HostedSessionResult =
|
|
|
119
119
|
cause?: unknown;
|
|
120
120
|
};
|
|
121
121
|
/**
|
|
122
|
-
* Exchange the hosted-login cookie on an incoming request for a
|
|
122
|
+
* Exchange the hosted-login cookie on an incoming request for a user
|
|
123
123
|
* bearer + identity (server-to-server: forwards the cookie to the front door's
|
|
124
124
|
* session endpoint with the CSRF header).
|
|
125
125
|
*
|
|
@@ -127,3 +127,27 @@ export type HostedSessionResult =
|
|
|
127
127
|
* returning `null` for everything that is not a success.
|
|
128
128
|
*/
|
|
129
129
|
export declare function fetchHostedSession(request: Request, config: HostedLoginConfig): Promise<HostedSessionResult>;
|
|
130
|
+
/**
|
|
131
|
+
* What the hosted login said beside the code when it sent someone back to your
|
|
132
|
+
* app. A sign-in it refused carries a stable code (`foir_error`); one that
|
|
133
|
+
* connected a provider to the person's account on the way in names the
|
|
134
|
+
* provider (`foir_connected`). Each comes with a sentence your app can show as
|
|
135
|
+
* it is, so an app that does nothing more still shows words and never a code.
|
|
136
|
+
* Show your own words for a code by switching on `code`.
|
|
137
|
+
*/
|
|
138
|
+
export type HostedSignInNotice = {
|
|
139
|
+
kind: 'refused';
|
|
140
|
+
code: string;
|
|
141
|
+
message: string;
|
|
142
|
+
} | {
|
|
143
|
+
kind: 'connected';
|
|
144
|
+
provider: string;
|
|
145
|
+
message: string;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Reads the notice off the address the hosted login sent the browser back to:
|
|
149
|
+
* your callback URL, its search string, or its parameters. `names` maps a
|
|
150
|
+
* provider's key to the name to show (from `getAuthProviders`); without it the
|
|
151
|
+
* key is shown. Null when the hosted login said nothing.
|
|
152
|
+
*/
|
|
153
|
+
export declare function hostedSignInNoticeOf(location: string | URL | URLSearchParams, names?: Record<string, string>): HostedSignInNotice | null;
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@foir/sdk/auth
|
|
2
|
+
* `@foir/sdk/auth`: framework-agnostic sign-in for your app's users.
|
|
3
3
|
*
|
|
4
|
-
* The whole
|
|
5
|
-
* refresh, password management, email verification and account deletion
|
|
4
|
+
* The whole sign-in surface (login, register, OTP, external providers,
|
|
5
|
+
* refresh, password management, email verification and account deletion) over
|
|
6
6
|
* the server's generated `auth` namespace, plus token lifecycle with an
|
|
7
7
|
* injected `SessionAdapter`, guards, and the optional hosted-login bridge.
|
|
8
8
|
*
|
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
* the caller's own manifest advertises, through the same op transport
|
|
11
11
|
* `@foir/sdk/data` uses, so `graphql` is not on this module's path at all.
|
|
12
12
|
*/
|
|
13
|
-
export {
|
|
14
|
-
export type { FoirAuthTokens,
|
|
15
|
-
export {
|
|
13
|
+
export { login, register, requestOTP, loginWithOTP, refreshTokens, loginWithProvider, providerCallback, providerVerifyOTP, providerSignInRefusalOf, signInRefusalOf, confirmProviderLink, resendProviderLinkCode, getAuthProviders, getAuthConfig, getDefaultAuthProvider, signupContext, joinWaitlist, requestPasswordReset, resetPassword, updatePassword, verifyEmail, currentUser, myProviderToken, logout, deleteMyAccount, updateMyProfile, myAuthorizedApps, revokeAuthorizedApp, mySessions, endMySession, signOutEverywhere, createPersonalAccessToken, mySignInMethods, connectSignInMethod, disconnectSignInMethod, myFactors, beginStepUp, completeStepUp, beginFactorEnrollment, completeFactorEnrollment, removeFactor, makeRecoveryCodes, myOrganizations, registerOrganization, saveSession, clearSession, getSessionToken, getSessionUser, signInWithPassword, signUpWithPassword, signInWithOTP, resetPasswordAndSignIn, refreshSession, refreshSessionOnce, signOut, authedRequest, authedOp, requireAuth, } from './auth';
|
|
14
|
+
export type { FoirAuthTokens, FoirUser, SessionAdapter, RequireAuthOptions, } from './auth';
|
|
15
|
+
export { userAccess, getSessionAccess, hasRole, hasScope, requireRole, requireScope, FoirAccessDeniedError, } from './access';
|
|
16
16
|
export type { FoirAccess, FoirAccessDenial } from './access';
|
|
17
17
|
export { FoirAuthDowngradedError, FoirSessionExpiredError } from '../graphql/foir-request';
|
|
18
18
|
export { AUTH_OPS } from './ops';
|
|
19
19
|
export type { AuthOpName, AuthOpOptions } from './ops';
|
|
20
|
-
export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession } from './hosted';
|
|
21
|
-
export type { HostedLoginConfig, HostedSession, HostedSessionResult } from './hosted';
|
|
20
|
+
export { hostedLoginUrl, hostedLogoutUrl, fetchHostedSession, hostedSignInNoticeOf } from './hosted';
|
|
21
|
+
export type { HostedLoginConfig, HostedSession, HostedSessionResult, HostedSignInNotice } from './hosted';
|
|
22
22
|
export { createFoirSessionClient } from './session-client';
|
|
23
23
|
export type { FoirSessionClient, FoirSessionConfig, FoirSessionUser, } from './session-client';
|
|
24
|
-
export type {
|
|
24
|
+
export type { AuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, WaitlistJoinResult, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, ProviderTokenRefresh, DeleteAccountResult, MyOrganization, MyProfile, MyProfileChange, AuthorizedApp, MySession, NewPersonalAccessToken, MySignInMethod, MyFactor, StepUp, FactorEnrollment, } from './operations';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Foir
|
|
2
|
+
* Foir sign-in result and input types.
|
|
3
3
|
*
|
|
4
4
|
* These are PLATFORM-STABLE primitives — login / register / refresh / OTP /
|
|
5
5
|
* provider login / password management / account lifecycle are fixed-shape
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
* and they are the reason `@foir/sdk/auth/session` had to exist as a
|
|
26
26
|
* separate graphql-free door.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
* here
|
|
28
|
+
* Anything project-specific about the user (a profile record, say) is NOT
|
|
29
|
+
* here: that shape varies per project, so it stays consumer-injected (see
|
|
30
30
|
* `requireAuth`'s `resolveUser`).
|
|
31
31
|
*/
|
|
32
32
|
/** The common auth result every login/register/refresh mutation returns. */
|
|
33
|
-
export interface
|
|
33
|
+
export interface AuthResult {
|
|
34
34
|
token: string;
|
|
35
35
|
refreshToken: string;
|
|
36
|
-
|
|
36
|
+
userId: string;
|
|
37
37
|
email: string;
|
|
38
38
|
}
|
|
39
39
|
/** Result of initiating a provider login — a redirect URL or an inline token. */
|
|
@@ -70,7 +70,7 @@ export interface ProviderOTPVerifyInput {
|
|
|
70
70
|
email: string;
|
|
71
71
|
otp: string;
|
|
72
72
|
}
|
|
73
|
-
/** The project's
|
|
73
|
+
/** The project's signup mode. */
|
|
74
74
|
export type SignupMode = 'PUBLIC' | 'RESTRICTED' | 'WAITLIST' | 'CLOSED';
|
|
75
75
|
/** The signup surface a self-hosted frontend reads to render the right UI. */
|
|
76
76
|
export interface SignupContext {
|
|
@@ -93,7 +93,7 @@ export interface AuthConfigProvider {
|
|
|
93
93
|
/**
|
|
94
94
|
* The project's public auth configuration: which built-in methods are on, and
|
|
95
95
|
* which external providers a login screen should offer. Readable before any
|
|
96
|
-
*
|
|
96
|
+
* user token exists: it is what a login screen renders itself from.
|
|
97
97
|
*/
|
|
98
98
|
export interface AuthConfig {
|
|
99
99
|
passwordEnabled: boolean;
|
|
@@ -110,8 +110,8 @@ export interface DefaultAuthProvider {
|
|
|
110
110
|
type: string | null;
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
* The calling
|
|
114
|
-
* carried no
|
|
113
|
+
* The calling user, resolved from the bearer token. `null` when the request
|
|
114
|
+
* carried no user token: an absent session is an answer, not a failure.
|
|
115
115
|
*/
|
|
116
116
|
export interface CurrentUser {
|
|
117
117
|
id: string;
|
|
@@ -123,18 +123,33 @@ export interface CurrentUser {
|
|
|
123
123
|
/** ISO-8601. */
|
|
124
124
|
createdAt: string;
|
|
125
125
|
}
|
|
126
|
+
/** What a provider token read did about the token's freshness. */
|
|
127
|
+
export type ProviderTokenRefresh = 'NOT_ATTEMPTED' | 'REFRESHED' | 'FAILED' | 'HELD_BACK';
|
|
126
128
|
/**
|
|
127
|
-
* A stored upstream provider token for the calling
|
|
128
|
-
* is no
|
|
129
|
+
* A stored upstream provider token for the calling user. `myProviderToken`
|
|
130
|
+
* answers `null` when there is no user token on the request, and
|
|
131
|
+
* `linked: false` when the user has none stored for that provider.
|
|
129
132
|
*/
|
|
130
133
|
export interface ProviderToken {
|
|
131
134
|
providerKey: string;
|
|
132
|
-
|
|
135
|
+
/** False when the user has no token for the provider; every token field is then null. */
|
|
136
|
+
linked: boolean;
|
|
137
|
+
accessToken: string | null;
|
|
133
138
|
refreshToken: string | null;
|
|
134
139
|
/** ISO-8601. */
|
|
135
140
|
expiresAt: string | null;
|
|
136
141
|
/** Space-separated provider scopes, as the provider issued them. */
|
|
137
142
|
scopes: string | null;
|
|
143
|
+
/**
|
|
144
|
+
* What the read did: `REFRESHED` renewed it, `FAILED` tried and served the
|
|
145
|
+
* stored token, `HELD_BACK` had tried within the last minute, and
|
|
146
|
+
* `NOT_ATTEMPTED` found it fresh or had nothing to renew it with.
|
|
147
|
+
*/
|
|
148
|
+
refresh: ProviderTokenRefresh | null;
|
|
149
|
+
/** Where a failed refresh failed, such as `"refresh grant"`. */
|
|
150
|
+
refreshFailure: string | null;
|
|
151
|
+
/** Only the user signing in with the provider again mends the connection. */
|
|
152
|
+
reauthenticate: boolean;
|
|
138
153
|
}
|
|
139
154
|
/** What a self-serve account deletion removed. */
|
|
140
155
|
export interface DeleteAccountResult {
|
|
@@ -142,11 +157,122 @@ export interface DeleteAccountResult {
|
|
|
142
157
|
recordsDeleted: number;
|
|
143
158
|
filesDeleted: number;
|
|
144
159
|
}
|
|
160
|
+
/** The calling user's own profile, as `updateMyProfile` answers it. */
|
|
161
|
+
export interface MyProfile {
|
|
162
|
+
id: string;
|
|
163
|
+
email: string;
|
|
164
|
+
firstName: string | null;
|
|
165
|
+
lastName: string | null;
|
|
166
|
+
avatarUrl: string | null;
|
|
167
|
+
/** BCP47, or null where they follow the project default. */
|
|
168
|
+
preferredLocale: string | null;
|
|
169
|
+
}
|
|
145
170
|
/**
|
|
146
|
-
*
|
|
171
|
+
* What `updateMyProfile` changes. A field left out stays as it is; an empty
|
|
172
|
+
* string clears it.
|
|
173
|
+
*/
|
|
174
|
+
export type MyProfileChange = {
|
|
175
|
+
firstName?: string;
|
|
176
|
+
lastName?: string;
|
|
177
|
+
avatarUrl?: string;
|
|
178
|
+
/** A locale the project publishes. */
|
|
179
|
+
preferredLocale?: string;
|
|
180
|
+
};
|
|
181
|
+
/** Somewhere the calling user is signed in, or a personal access token they hold. */
|
|
182
|
+
export interface MySession {
|
|
183
|
+
/** The session's handle, which {@link endMySession} takes. It signs nobody in. */
|
|
184
|
+
id: string;
|
|
185
|
+
/** `browser` for a sign-in, `personal_access_token` for a token. */
|
|
186
|
+
kind: string;
|
|
187
|
+
/** A personal access token's name. */
|
|
188
|
+
name: string | null;
|
|
189
|
+
/** The first characters of a personal access token's value. */
|
|
190
|
+
tokenPrefix: string | null;
|
|
191
|
+
/** The app it was signed in through. */
|
|
192
|
+
appName: string | null;
|
|
193
|
+
/** ISO-8601. */
|
|
194
|
+
createdAt: string | null;
|
|
195
|
+
/** ISO-8601. */
|
|
196
|
+
lastSeenAt: string | null;
|
|
197
|
+
/** ISO-8601. */
|
|
198
|
+
expiresAt: string | null;
|
|
199
|
+
ipAddress: string | null;
|
|
200
|
+
userAgent: string | null;
|
|
201
|
+
/** Whether it is the session making the request. */
|
|
202
|
+
current: boolean;
|
|
203
|
+
}
|
|
204
|
+
/** A personal access token just made: its value is shown here once. */
|
|
205
|
+
export interface NewPersonalAccessToken {
|
|
206
|
+
token: MySession;
|
|
207
|
+
value: string;
|
|
208
|
+
}
|
|
209
|
+
/** A provider the calling user signs in with. */
|
|
210
|
+
export interface MySignInMethod {
|
|
211
|
+
provider: string;
|
|
212
|
+
/** The address the provider gave when it was connected. */
|
|
213
|
+
email: string | null;
|
|
214
|
+
/** ISO-8601. */
|
|
215
|
+
connectedAt: string | null;
|
|
216
|
+
}
|
|
217
|
+
/** A second factor, passkey or set of recovery codes the calling user holds. */
|
|
218
|
+
export interface MyFactor {
|
|
219
|
+
/** `totp`, `security_key`, `passkey` or `recovery_codes`. */
|
|
220
|
+
kind: string;
|
|
221
|
+
/** What {@link removeFactor} takes. */
|
|
222
|
+
id: string;
|
|
223
|
+
/**
|
|
224
|
+
* A security key's or passkey's name. A passkey made on the project's own
|
|
225
|
+
* login domain is named by that domain, the only one it signs in on.
|
|
226
|
+
*/
|
|
227
|
+
name: string | null;
|
|
228
|
+
/** ISO-8601. */
|
|
229
|
+
addedAt: string | null;
|
|
230
|
+
/** For recovery codes: how many are left unused. */
|
|
231
|
+
remaining: number | null;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* A step-up begun with the calling user's password: waiting for a second
|
|
235
|
+
* factor, or, for a user who holds none, already proven by the password.
|
|
236
|
+
*/
|
|
237
|
+
export interface StepUp {
|
|
238
|
+
/** What {@link completeStepUp} takes, for a user who holds a second factor. */
|
|
239
|
+
handle: string | null;
|
|
240
|
+
/** For a user who holds no second factor: the proof their password is, for one change to their factors. */
|
|
241
|
+
elevatedHandle: string | null;
|
|
242
|
+
/** Whether a security key or passkey can prove it. */
|
|
243
|
+
webauthn: boolean;
|
|
244
|
+
/** Whether an authenticator app's code can prove it. */
|
|
245
|
+
totp: boolean;
|
|
246
|
+
/** The publicKey options for `navigator.credentials.get`, as JSON. */
|
|
247
|
+
webauthnOptionsJson: string | null;
|
|
248
|
+
}
|
|
249
|
+
/** A second factor being added, waiting for the browser or the authenticator app. */
|
|
250
|
+
export interface FactorEnrollment {
|
|
251
|
+
/** What {@link completeFactorEnrollment} takes. */
|
|
252
|
+
handle: string;
|
|
253
|
+
/** For a security key or passkey: the publicKey options for `navigator.credentials.create`, as JSON. */
|
|
254
|
+
webauthnOptionsJson: string | null;
|
|
255
|
+
/** For an authenticator app: the key to enter by hand. */
|
|
256
|
+
totpSecret: string | null;
|
|
257
|
+
/** For an authenticator app: a QR code of the key, as a data URI. */
|
|
258
|
+
totpQr: string | null;
|
|
259
|
+
}
|
|
260
|
+
/** An app the calling user has agreed may act for them. */
|
|
261
|
+
export interface AuthorizedApp {
|
|
262
|
+
/** The consent's id, which {@link revokeAuthorizedApp} takes. */
|
|
263
|
+
id: string;
|
|
264
|
+
clientId: string;
|
|
265
|
+
clientName: string;
|
|
266
|
+
/** What the app may carry for them, in the permission catalogue's names. */
|
|
267
|
+
grants: string[];
|
|
268
|
+
/** ISO-8601. */
|
|
269
|
+
grantedAt: string | null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* One organization the calling user may stand in, as `myOrganizations`
|
|
147
273
|
* returns it.
|
|
148
274
|
*
|
|
149
|
-
* Identity, not configuration: the id
|
|
275
|
+
* Identity, not configuration: the id a request names to act there, a
|
|
150
276
|
* name to render, the status, and where it sits. Only active organizations are
|
|
151
277
|
* returned today; `status` is here so that a widening is visible to the caller
|
|
152
278
|
* rather than silent.
|
|
@@ -157,7 +283,7 @@ export interface DeleteAccountResult {
|
|
|
157
283
|
* this surface's to hand out.
|
|
158
284
|
*
|
|
159
285
|
* THE LIST IS A SUBTREE. A membership at an organization reaches everything
|
|
160
|
-
* beneath it, so
|
|
286
|
+
* beneath it, so someone who runs a group is offered the group and every
|
|
161
287
|
* client under it. Draw it flat and a reseller's clients render as its
|
|
162
288
|
* siblings, which is what the three position fields are for.
|
|
163
289
|
*/
|
|
@@ -168,7 +294,7 @@ export interface MyOrganization {
|
|
|
168
294
|
/**
|
|
169
295
|
* The immediate parent, or null for a root.
|
|
170
296
|
*
|
|
171
|
-
* A root OF THE PROJECT, not of the caller's view:
|
|
297
|
+
* A root OF THE PROJECT, not of the caller's view: a member whose
|
|
172
298
|
* membership sits three levels down sees their own organization with a parent
|
|
173
299
|
* they cannot read. Null means there is no parent; it is never the empty
|
|
174
300
|
* string, because that is a third value with no reading.
|
|
@@ -179,16 +305,16 @@ export interface MyOrganization {
|
|
|
179
305
|
* project root.
|
|
180
306
|
*
|
|
181
307
|
* IDS, not names, and the whole path — including ancestors this caller cannot
|
|
182
|
-
* read. An id is disclosed to everything below it; a name is row content
|
|
183
|
-
*
|
|
184
|
-
* readable ancestors would have gaps or a false root, and two
|
|
308
|
+
* read. An id is disclosed to everything below it; a name is row content a
|
|
309
|
+
* member further down may not be entitled to. A path trimmed to the
|
|
310
|
+
* readable ancestors would have gaps or a false root, and two members of
|
|
185
311
|
* one organization would then see different paths to it.
|
|
186
312
|
*/
|
|
187
313
|
ancestorIds: string[];
|
|
188
314
|
/**
|
|
189
315
|
* Levels above this organization in the PROJECT's tree: 0 is a project root.
|
|
190
316
|
*
|
|
191
|
-
* The project's depth rather than the caller's, so two
|
|
317
|
+
* The project's depth rather than the caller's, so two members looking at
|
|
192
318
|
* the same organization agree about where it is. Indent by the difference
|
|
193
319
|
* from the shallowest entry in the list if you want the caller's own view.
|
|
194
320
|
*/
|
|
@@ -197,7 +323,7 @@ export interface MyOrganization {
|
|
|
197
323
|
* The caller's own role in this organization, or null when their
|
|
198
324
|
* membership carries none (or a disabled one). Never another member's role.
|
|
199
325
|
*
|
|
200
|
-
* ONE ROLE, NOT THE WHOLE AUTHORITY. The platform resolves the
|
|
326
|
+
* ONE ROLE, NOT THE WHOLE AUTHORITY. The platform resolves the member's
|
|
201
327
|
* authority per request as the union of the roles on every membership that
|
|
202
328
|
* reaches this organization (one held in it and every one held above it),
|
|
203
329
|
* together with their own roles in the project. This field is the role of
|
|
@@ -206,6 +332,12 @@ export interface MyOrganization {
|
|
|
206
332
|
* sees part of what the next request is allowed and not the whole of it.
|
|
207
333
|
*/
|
|
208
334
|
role: MyOrganizationRole | null;
|
|
335
|
+
/**
|
|
336
|
+
* Where the list goes on from: pass the last entry's cursor as `after` to
|
|
337
|
+
* read the next page. Null on an entry no list gave, such as the one
|
|
338
|
+
* `registerOrganization` returns.
|
|
339
|
+
*/
|
|
340
|
+
cursor: string | null;
|
|
209
341
|
}
|
|
210
342
|
/** A role as `myOrganizations` reports it: enough to match a policy and to render. */
|
|
211
343
|
export interface MyOrganizationRole {
|
|
@@ -219,15 +351,3 @@ export interface MyOrganizationRole {
|
|
|
219
351
|
*/
|
|
220
352
|
grants: string[];
|
|
221
353
|
}
|
|
222
|
-
/**
|
|
223
|
-
* Where the caller now stands, as `selectMyOrganization` returns it.
|
|
224
|
-
*
|
|
225
|
-
* Two fields, because two are all there is to say. It is NOT a session context:
|
|
226
|
-
* an operator is a customer, and the platform's admin session shape — user,
|
|
227
|
-
* available tenants, effective permissions — describes a population this level
|
|
228
|
-
* exists to keep them out of.
|
|
229
|
-
*/
|
|
230
|
-
export interface SelectedOrganization {
|
|
231
|
-
orgId: string;
|
|
232
|
-
orgName: string;
|
|
233
|
-
}
|
package/dist/auth/ops.d.ts
CHANGED
|
@@ -41,37 +41,55 @@ export declare const AUTH_OPS: {
|
|
|
41
41
|
readonly authConfig: "authConfig";
|
|
42
42
|
readonly authProviders: "authProviders";
|
|
43
43
|
readonly defaultAuthProvider: "defaultAuthProvider";
|
|
44
|
-
readonly
|
|
44
|
+
readonly signupContext: "signupContext";
|
|
45
45
|
readonly currentUser: "currentUser";
|
|
46
46
|
readonly myProviderToken: "myProviderToken";
|
|
47
47
|
readonly whoami: "whoami";
|
|
48
|
-
readonly
|
|
49
|
-
readonly
|
|
50
|
-
readonly
|
|
51
|
-
readonly
|
|
52
|
-
readonly
|
|
53
|
-
readonly
|
|
54
|
-
readonly
|
|
55
|
-
readonly
|
|
56
|
-
readonly
|
|
57
|
-
readonly
|
|
58
|
-
readonly
|
|
48
|
+
readonly login: "login";
|
|
49
|
+
readonly register: "register";
|
|
50
|
+
readonly requestOTP: "requestOTP";
|
|
51
|
+
readonly loginWithOTP: "loginWithOTP";
|
|
52
|
+
readonly refreshToken: "refreshToken";
|
|
53
|
+
readonly logout: "logout";
|
|
54
|
+
readonly joinWaitlist: "joinWaitlist";
|
|
55
|
+
readonly requestPasswordReset: "requestPasswordReset";
|
|
56
|
+
readonly resetPassword: "resetPassword";
|
|
57
|
+
readonly updatePassword: "updatePassword";
|
|
58
|
+
readonly verifyEmail: "verifyEmail";
|
|
59
59
|
readonly deleteMyAccount: "deleteMyAccount";
|
|
60
|
+
readonly updateMyProfile: "updateMyProfile";
|
|
61
|
+
readonly myAuthorizedApps: "myAuthorizedApps";
|
|
62
|
+
readonly revokeAuthorizedApp: "revokeAuthorizedApp";
|
|
63
|
+
readonly mySessions: "mySessions";
|
|
64
|
+
readonly endMySession: "endMySession";
|
|
65
|
+
readonly signOutEverywhere: "signOutEverywhere";
|
|
66
|
+
readonly createPersonalAccessToken: "createPersonalAccessToken";
|
|
67
|
+
readonly mySignInMethods: "mySignInMethods";
|
|
68
|
+
readonly connectSignInMethod: "connectSignInMethod";
|
|
69
|
+
readonly disconnectSignInMethod: "disconnectSignInMethod";
|
|
70
|
+
readonly myFactors: "myFactors";
|
|
71
|
+
readonly beginStepUp: "beginStepUp";
|
|
72
|
+
readonly completeStepUp: "completeStepUp";
|
|
73
|
+
readonly beginFactorEnrollment: "beginFactorEnrollment";
|
|
74
|
+
readonly completeFactorEnrollment: "completeFactorEnrollment";
|
|
75
|
+
readonly removeFactor: "removeFactor";
|
|
76
|
+
readonly makeRecoveryCodes: "makeRecoveryCodes";
|
|
60
77
|
readonly myOrganizations: "myOrganizations";
|
|
61
|
-
readonly
|
|
62
|
-
readonly
|
|
63
|
-
readonly
|
|
64
|
-
readonly
|
|
65
|
-
readonly
|
|
78
|
+
readonly registerOrganization: "registerOrganization";
|
|
79
|
+
readonly loginWithProvider: "loginWithProvider";
|
|
80
|
+
readonly providerCallback: "providerCallback";
|
|
81
|
+
readonly providerVerifyOTP: "providerVerifyOTP";
|
|
82
|
+
readonly confirmProviderLink: "confirmProviderLink";
|
|
83
|
+
readonly resendProviderLinkCode: "resendProviderLinkCode";
|
|
66
84
|
};
|
|
67
85
|
/** One of the auth namespace's root field names. */
|
|
68
86
|
export type AuthOpName = (typeof AUTH_OPS)[keyof typeof AUTH_OPS];
|
|
69
87
|
/** Per-call overrides for an auth op. */
|
|
70
88
|
export interface AuthOpOptions {
|
|
71
89
|
/**
|
|
72
|
-
*
|
|
73
|
-
* the project; the bearer adds the
|
|
74
|
-
* (`currentUser`, `
|
|
90
|
+
* User bearer token. api-public still needs the `x-api-key` to resolve
|
|
91
|
+
* the project; the bearer adds the user identity the post-credential ops
|
|
92
|
+
* (`currentUser`, `updatePassword`, `deleteMyAccount`, …) are
|
|
75
93
|
* authorised by. Ignored by the pre-credential ops, which mint it.
|
|
76
94
|
*/
|
|
77
95
|
bearerToken?: string;
|
|
@@ -85,7 +103,7 @@ export interface AuthOpOptions {
|
|
|
85
103
|
/**
|
|
86
104
|
* The organization the call acts in, named by the address of the page it is
|
|
87
105
|
* for, sent as `X-Org-Url`: same contract as `FoirClientConfig.orgUrl`. A
|
|
88
|
-
* storefront on a path per organization signs its
|
|
106
|
+
* storefront on a path per organization signs its users in on those
|
|
89
107
|
* pages, and names the organization the way every other request from them
|
|
90
108
|
* does.
|
|
91
109
|
*/
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* There is no refresh token in this flow. Re-exchanging the cookie IS the
|
|
20
20
|
* refresh, which is why nothing here needs rotation or reuse-detection: the
|
|
21
21
|
* durable credential is an opaque server-side session handle, revocable at any
|
|
22
|
-
* time from the
|
|
22
|
+
* time from the console or `signOut()`.
|
|
23
23
|
*
|
|
24
24
|
* REQUIRES a verified custom login domain that shares a registrable domain with
|
|
25
25
|
* your app (app.acme.com + login.acme.com). That is what makes the cookie
|
|
@@ -47,7 +47,7 @@ export interface FoirSessionConfig {
|
|
|
47
47
|
}
|
|
48
48
|
/** The identity behind the current session. */
|
|
49
49
|
export interface FoirSessionUser {
|
|
50
|
-
|
|
50
|
+
userId: string;
|
|
51
51
|
email: string;
|
|
52
52
|
}
|
|
53
53
|
export interface FoirSessionClient {
|
|
@@ -57,7 +57,7 @@ export interface FoirSessionClient {
|
|
|
57
57
|
* exchange. Call this before each API request — do not cache the result.
|
|
58
58
|
*/
|
|
59
59
|
getToken(): Promise<string | null>;
|
|
60
|
-
/** The signed-in
|
|
60
|
+
/** The signed-in user, or null. Exchanges if the session isn't loaded yet. */
|
|
61
61
|
getUser(): Promise<FoirSessionUser | null>;
|
|
62
62
|
/** Revoke the session server-side and clear the cookie. Idempotent. */
|
|
63
63
|
signOut(): Promise<void>;
|
package/dist/auth-session.js
CHANGED
|
@@ -64,14 +64,14 @@ function createFoirSessionClient(config) {
|
|
|
64
64
|
} catch {
|
|
65
65
|
return cached;
|
|
66
66
|
}
|
|
67
|
-
if (!json.access_token || !json.
|
|
67
|
+
if (!json.access_token || !json.user_id) return cached;
|
|
68
68
|
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 0;
|
|
69
69
|
const next = {
|
|
70
70
|
token: json.access_token,
|
|
71
71
|
expiresAt: Date.now() + expiresIn * 1e3,
|
|
72
|
-
user: {
|
|
72
|
+
user: { userId: json.user_id, email: json.email ?? "" }
|
|
73
73
|
};
|
|
74
|
-
const changed = cached?.user.
|
|
74
|
+
const changed = cached?.user.userId !== next.user.userId;
|
|
75
75
|
cached = next;
|
|
76
76
|
scheduleRefresh(next.expiresAt);
|
|
77
77
|
if (changed) emit(next.user);
|