@ai-matrx/auth 0.1.4 → 0.2.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 +22 -0
- package/README.md +11 -0
- package/dist/claims-DXdbe3cW.d.cts +138 -0
- package/dist/claims-DXdbe3cW.d.ts +138 -0
- package/dist/index.cjs +135 -1
- package/dist/index.d.cts +4 -51
- package/dist/index.d.ts +4 -51
- package/dist/index.js +138 -1
- package/dist/matrx-oauth.cjs +458 -0
- package/dist/matrx-oauth.d.cts +181 -0
- package/dist/matrx-oauth.d.ts +181 -0
- package/dist/matrx-oauth.js +437 -0
- package/dist/next.cjs +133 -1
- package/dist/next.d.cts +77 -1
- package/dist/next.d.ts +77 -1
- package/dist/next.js +136 -1
- package/dist/node.cjs +139 -2
- package/dist/node.d.cts +117 -1
- package/dist/node.d.ts +117 -1
- package/dist/node.js +136 -1
- package/dist/organizations.cjs +94 -0
- package/dist/organizations.d.cts +107 -1
- package/dist/organizations.d.ts +107 -1
- package/dist/organizations.js +94 -0
- package/dist/storage-Dqnye4KF.d.cts +51 -0
- package/dist/storage-Dqnye4KF.d.ts +51 -0
- package/package.json +12 -2
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { S as SessionStorage } from './storage-Dqnye4KF.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "Sign in with AI Matrx" — OAuth 2.1 authorization-code + PKCE (S256) against the AI Matrx auth
|
|
5
|
+
* server (Supabase's OAuth 2.1 server on https://db.matrxserver.com), written ONCE, host-neutral.
|
|
6
|
+
*
|
|
7
|
+
* Two apps used to carry hand-made copies; both now import this:
|
|
8
|
+
* - Chrome extension (matrx-extend/src/lib/auth/flow.ts): storage = chrome.storage, redirect
|
|
9
|
+
* captured by chrome.identity.launchWebAuthFlow, cross-context `navigator.locks`, AES-GCM
|
|
10
|
+
* sealed refresh token.
|
|
11
|
+
* - Desktop (matrx-desktop/app/main/auth): storage = the OS-sealed vault, authorize URL opened
|
|
12
|
+
* in the system browser, redirect captured by a loopback server on localhost.
|
|
13
|
+
*
|
|
14
|
+
* NOTHING host-specific is imported here (no chrome.*, electron, node:*, window). The host injects
|
|
15
|
+
* ports: `storage` (the package's SessionStorage shape), `openAuthorization`, and optionally
|
|
16
|
+
* `fetch`, `now`, `lock`, `seal`, `pendingStorage`, `beforeCommit`. Runs on WebCrypto
|
|
17
|
+
* (browser, extension service worker, Node >= 20).
|
|
18
|
+
*
|
|
19
|
+
* Config each app passes in (values they use today; this module hard-codes none of the ids):
|
|
20
|
+
* authUrl both: https://db.matrxserver.com (endpoints: /auth/v1/oauth/{authorize,token},
|
|
21
|
+
* /auth/v1/logout?scope=local)
|
|
22
|
+
* clientId extension: its own public PKCE client (env WXT_EXTENSION_OAUTH_CLIENT_ID);
|
|
23
|
+
* desktop: "Matrx 2" public client (see matrx-desktop/app/main/auth/config.ts)
|
|
24
|
+
* redirectUri extension: chrome.identity.getRedirectURL() = https://<id>.chromiumapp.org/
|
|
25
|
+
* (Safari: https://www.aimatrx.com/auth/extension-callback);
|
|
26
|
+
* desktop: http://localhost:22140/auth/callback (dev: 22240)
|
|
27
|
+
* scope both: "email profile". NEVER add "openid": the server then tries to mint an
|
|
28
|
+
* ID token and answers 500 "Error generating ID token".
|
|
29
|
+
* publishableKey desktop sends it as `apikey` on token + logout calls; extension on logout.
|
|
30
|
+
* Optional here; sent whenever given.
|
|
31
|
+
* refreshLeadMs extension 60_000, desktop 120_000 (default 60_000)
|
|
32
|
+
*
|
|
33
|
+
* Behaviour kept from the extension: verifier PERSISTED locally and only a random nonce travels as
|
|
34
|
+
* `state` (never the verifier in the URL); one verifier per attempt keyed by state; a newer
|
|
35
|
+
* attempt/sign-out supersedes an older one; state mismatch refused; the exact user-facing
|
|
36
|
+
* sentences; the cross-context refresh race guard (if the stored refresh token changed while our
|
|
37
|
+
* refresh was refused, adopt the winner instead of signing out); optional pre-commit validation.
|
|
38
|
+
* Kept from the desktop: constant-time state compare, 10 min attempt expiry, terminal-vs-transient
|
|
39
|
+
* error split (a refused grant ends the session; a network blip / 5xx / captive portal keeps it),
|
|
40
|
+
* HTML-on-200 is never a token, client-changed sessions dropped, refresh timeout, person id from JWT.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
type MatrxOAuthErrorCode = "config_missing" | "state_mismatch" | "callback_error" | "callback_incomplete" | "attempt_expired" | "verifier_lost" | "superseded" | "token_exchange_failed" | "invalid_token" | "grant_refused" | "transient" | "unsupported_environment";
|
|
44
|
+
declare class MatrxOAuthError extends Error {
|
|
45
|
+
readonly code: MatrxOAuthErrorCode;
|
|
46
|
+
constructor(code: MatrxOAuthErrorCode, message: string);
|
|
47
|
+
}
|
|
48
|
+
/** The server refused the grant (revoked, reused, expired): the session is over. Never retried. */
|
|
49
|
+
declare class GrantRefusedError extends MatrxOAuthError {
|
|
50
|
+
readonly serverCode: string;
|
|
51
|
+
constructor(serverCode: string, detail: string);
|
|
52
|
+
}
|
|
53
|
+
/** Network down, captive portal, 5xx, 429: try again later; the session is still good. */
|
|
54
|
+
declare class TransientAuthError extends MatrxOAuthError {
|
|
55
|
+
constructor(detail: string);
|
|
56
|
+
}
|
|
57
|
+
/** RFC 7636 §4.1: 43 chars of base64url from 32 random bytes. */
|
|
58
|
+
declare function generateCodeVerifier(): string;
|
|
59
|
+
/** RFC 7636 §4.2 S256: BASE64URL(SHA256(ASCII(verifier))). */
|
|
60
|
+
declare function codeChallengeFor(verifier: string): Promise<string>;
|
|
61
|
+
/** The `state` value: 256 random bits, ties the callback to this attempt. */
|
|
62
|
+
declare function generateState(): string;
|
|
63
|
+
/** Constant-time string comparison. */
|
|
64
|
+
declare function constantTimeEqual(a: string, b: string): boolean;
|
|
65
|
+
interface AuthorizeUrlInput {
|
|
66
|
+
authUrl: string;
|
|
67
|
+
clientId: string;
|
|
68
|
+
redirectUri: string;
|
|
69
|
+
state: string;
|
|
70
|
+
challenge: string;
|
|
71
|
+
scope: string;
|
|
72
|
+
}
|
|
73
|
+
declare function buildAuthorizeUrl(input: AuthorizeUrlInput): string;
|
|
74
|
+
/** Read `code` and `state` from the redirect the browser landed on; throws on error / missing parts. */
|
|
75
|
+
declare function parseCallbackUrl(callbackUrl: string): {
|
|
76
|
+
code: string;
|
|
77
|
+
returnedState: string;
|
|
78
|
+
};
|
|
79
|
+
interface AccessTokenClaims {
|
|
80
|
+
/** The person id (`sub`). */
|
|
81
|
+
personId: string;
|
|
82
|
+
email: string | null;
|
|
83
|
+
/** Expiry in ms since epoch, or null when the token carries no `exp`. */
|
|
84
|
+
expiresAtMs: number | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read `sub`, `email`, `exp` from an access token. The signature is NOT verified here: the token
|
|
88
|
+
* came straight from the issuer over TLS. Verify-on-receipt belongs to servers.
|
|
89
|
+
*/
|
|
90
|
+
declare function personIdFromAccessToken(token: string): AccessTokenClaims;
|
|
91
|
+
interface MatrxOAuthConfig {
|
|
92
|
+
/** Auth server origin, e.g. https://db.matrxserver.com */
|
|
93
|
+
authUrl: string;
|
|
94
|
+
/** This app's public PKCE client id. */
|
|
95
|
+
clientId: string;
|
|
96
|
+
redirectUri: string;
|
|
97
|
+
/** Default "email profile". Never include "openid". */
|
|
98
|
+
scope?: string;
|
|
99
|
+
/** Sent as `apikey` on token + logout calls when given. */
|
|
100
|
+
publishableKey?: string;
|
|
101
|
+
/** Refresh this long before expiry. Default 60_000. */
|
|
102
|
+
refreshLeadMs?: number;
|
|
103
|
+
/** A started sign-in expires after this. Default 600_000. */
|
|
104
|
+
attemptTtlMs?: number;
|
|
105
|
+
/** Token endpoint timeout. Default 15_000. */
|
|
106
|
+
requestTimeoutMs?: number;
|
|
107
|
+
/** Storage key namespace. Default "matrx-oauth". */
|
|
108
|
+
storageKeyPrefix?: string;
|
|
109
|
+
}
|
|
110
|
+
interface OpenAuthorizationContext {
|
|
111
|
+
state: string;
|
|
112
|
+
redirectUri: string;
|
|
113
|
+
}
|
|
114
|
+
interface MatrxOAuthPorts {
|
|
115
|
+
/** Where the session lives between runs (the package's SessionStorage shape). */
|
|
116
|
+
storage: SessionStorage;
|
|
117
|
+
/**
|
|
118
|
+
* Open the authorize URL and resolve with the full redirect URL the browser landed on.
|
|
119
|
+
* Reject if the person cancelled. Extension: chrome.identity.launchWebAuthFlow.
|
|
120
|
+
* Desktop: open the system browser and wait on the loopback server.
|
|
121
|
+
*/
|
|
122
|
+
openAuthorization(authorizeUrl: string, context: OpenAuthorizationContext): Promise<string>;
|
|
123
|
+
fetch?: typeof fetch;
|
|
124
|
+
now?: () => number;
|
|
125
|
+
/** Short-lived attempt state (verifier). Default: `storage`. Extension: chrome.storage.session. */
|
|
126
|
+
pendingStorage?: SessionStorage;
|
|
127
|
+
/** Serialises storage mutations across contexts (extension: navigator.locks). Default: in-process. */
|
|
128
|
+
lock?: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
129
|
+
/** Seal/unseal the refresh token at rest (extension: AES-GCM). Default: stored as-is. */
|
|
130
|
+
seal?: {
|
|
131
|
+
encrypt(plain: string): Promise<string>;
|
|
132
|
+
decrypt(sealed: string): Promise<string>;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Runs on freshly exchanged tokens BEFORE they displace a working session; throw to refuse
|
|
136
|
+
* (extension: GET /auth/v1/user). Never call per request.
|
|
137
|
+
*/
|
|
138
|
+
beforeCommit?: (accessToken: string) => Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
interface MatrxOAuthSession {
|
|
141
|
+
personId: string;
|
|
142
|
+
email: string | null;
|
|
143
|
+
accessToken: string;
|
|
144
|
+
expiresAtMs: number;
|
|
145
|
+
}
|
|
146
|
+
type MatrxOAuthEvent = {
|
|
147
|
+
type: "signed_in";
|
|
148
|
+
session: MatrxOAuthSession;
|
|
149
|
+
} | {
|
|
150
|
+
type: "refreshed";
|
|
151
|
+
session: MatrxOAuthSession;
|
|
152
|
+
} | {
|
|
153
|
+
type: "signed_out";
|
|
154
|
+
reason: "user" | "session_ended" | "client_changed" | "person_changed" | "unreadable";
|
|
155
|
+
};
|
|
156
|
+
interface TokenSet {
|
|
157
|
+
accessToken: string;
|
|
158
|
+
/** Null only if the server chose not to rotate it. */
|
|
159
|
+
refreshToken: string | null;
|
|
160
|
+
expiresAtMs: number;
|
|
161
|
+
}
|
|
162
|
+
interface MatrxOAuthClient {
|
|
163
|
+
/** Run the whole interactive sign-in. Resolves once tokens are stored. */
|
|
164
|
+
signIn(): Promise<MatrxOAuthSession>;
|
|
165
|
+
/** A usable access token, refreshing on expiry (single-flight). Null = signed out. Throws on transient failure with no valid token left. */
|
|
166
|
+
getAccessToken(): Promise<string | null>;
|
|
167
|
+
/** Force a refresh (alarms, 401 retry). Pass the token the server rejected to refuse adopting it. */
|
|
168
|
+
refresh(rejectedAccessToken?: string): Promise<string | null>;
|
|
169
|
+
/** The stored session without refreshing or touching the network. */
|
|
170
|
+
getSession(): Promise<MatrxOAuthSession | null>;
|
|
171
|
+
/** The person id from the stored access token, no network. Null = signed out. */
|
|
172
|
+
getPersonId(): Promise<string | null>;
|
|
173
|
+
/** Clear local state, then best-effort revoke on the server. Returns whether the server agreed (null = nothing to revoke). */
|
|
174
|
+
signOut(): Promise<{
|
|
175
|
+
revoked: boolean | null;
|
|
176
|
+
}>;
|
|
177
|
+
onChange(listener: (event: MatrxOAuthEvent) => void): () => void;
|
|
178
|
+
}
|
|
179
|
+
declare function createMatrxOAuth(config: MatrxOAuthConfig, ports: MatrxOAuthPorts): MatrxOAuthClient;
|
|
180
|
+
|
|
181
|
+
export { type AccessTokenClaims, type AuthorizeUrlInput, GrantRefusedError, type MatrxOAuthClient, type MatrxOAuthConfig, MatrxOAuthError, type MatrxOAuthErrorCode, type MatrxOAuthEvent, type MatrxOAuthPorts, type MatrxOAuthSession, type OpenAuthorizationContext, type TokenSet, TransientAuthError, buildAuthorizeUrl, codeChallengeFor, constantTimeEqual, createMatrxOAuth, generateCodeVerifier, generateState, parseCallbackUrl, personIdFromAccessToken };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { S as SessionStorage } from './storage-Dqnye4KF.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "Sign in with AI Matrx" — OAuth 2.1 authorization-code + PKCE (S256) against the AI Matrx auth
|
|
5
|
+
* server (Supabase's OAuth 2.1 server on https://db.matrxserver.com), written ONCE, host-neutral.
|
|
6
|
+
*
|
|
7
|
+
* Two apps used to carry hand-made copies; both now import this:
|
|
8
|
+
* - Chrome extension (matrx-extend/src/lib/auth/flow.ts): storage = chrome.storage, redirect
|
|
9
|
+
* captured by chrome.identity.launchWebAuthFlow, cross-context `navigator.locks`, AES-GCM
|
|
10
|
+
* sealed refresh token.
|
|
11
|
+
* - Desktop (matrx-desktop/app/main/auth): storage = the OS-sealed vault, authorize URL opened
|
|
12
|
+
* in the system browser, redirect captured by a loopback server on localhost.
|
|
13
|
+
*
|
|
14
|
+
* NOTHING host-specific is imported here (no chrome.*, electron, node:*, window). The host injects
|
|
15
|
+
* ports: `storage` (the package's SessionStorage shape), `openAuthorization`, and optionally
|
|
16
|
+
* `fetch`, `now`, `lock`, `seal`, `pendingStorage`, `beforeCommit`. Runs on WebCrypto
|
|
17
|
+
* (browser, extension service worker, Node >= 20).
|
|
18
|
+
*
|
|
19
|
+
* Config each app passes in (values they use today; this module hard-codes none of the ids):
|
|
20
|
+
* authUrl both: https://db.matrxserver.com (endpoints: /auth/v1/oauth/{authorize,token},
|
|
21
|
+
* /auth/v1/logout?scope=local)
|
|
22
|
+
* clientId extension: its own public PKCE client (env WXT_EXTENSION_OAUTH_CLIENT_ID);
|
|
23
|
+
* desktop: "Matrx 2" public client (see matrx-desktop/app/main/auth/config.ts)
|
|
24
|
+
* redirectUri extension: chrome.identity.getRedirectURL() = https://<id>.chromiumapp.org/
|
|
25
|
+
* (Safari: https://www.aimatrx.com/auth/extension-callback);
|
|
26
|
+
* desktop: http://localhost:22140/auth/callback (dev: 22240)
|
|
27
|
+
* scope both: "email profile". NEVER add "openid": the server then tries to mint an
|
|
28
|
+
* ID token and answers 500 "Error generating ID token".
|
|
29
|
+
* publishableKey desktop sends it as `apikey` on token + logout calls; extension on logout.
|
|
30
|
+
* Optional here; sent whenever given.
|
|
31
|
+
* refreshLeadMs extension 60_000, desktop 120_000 (default 60_000)
|
|
32
|
+
*
|
|
33
|
+
* Behaviour kept from the extension: verifier PERSISTED locally and only a random nonce travels as
|
|
34
|
+
* `state` (never the verifier in the URL); one verifier per attempt keyed by state; a newer
|
|
35
|
+
* attempt/sign-out supersedes an older one; state mismatch refused; the exact user-facing
|
|
36
|
+
* sentences; the cross-context refresh race guard (if the stored refresh token changed while our
|
|
37
|
+
* refresh was refused, adopt the winner instead of signing out); optional pre-commit validation.
|
|
38
|
+
* Kept from the desktop: constant-time state compare, 10 min attempt expiry, terminal-vs-transient
|
|
39
|
+
* error split (a refused grant ends the session; a network blip / 5xx / captive portal keeps it),
|
|
40
|
+
* HTML-on-200 is never a token, client-changed sessions dropped, refresh timeout, person id from JWT.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
type MatrxOAuthErrorCode = "config_missing" | "state_mismatch" | "callback_error" | "callback_incomplete" | "attempt_expired" | "verifier_lost" | "superseded" | "token_exchange_failed" | "invalid_token" | "grant_refused" | "transient" | "unsupported_environment";
|
|
44
|
+
declare class MatrxOAuthError extends Error {
|
|
45
|
+
readonly code: MatrxOAuthErrorCode;
|
|
46
|
+
constructor(code: MatrxOAuthErrorCode, message: string);
|
|
47
|
+
}
|
|
48
|
+
/** The server refused the grant (revoked, reused, expired): the session is over. Never retried. */
|
|
49
|
+
declare class GrantRefusedError extends MatrxOAuthError {
|
|
50
|
+
readonly serverCode: string;
|
|
51
|
+
constructor(serverCode: string, detail: string);
|
|
52
|
+
}
|
|
53
|
+
/** Network down, captive portal, 5xx, 429: try again later; the session is still good. */
|
|
54
|
+
declare class TransientAuthError extends MatrxOAuthError {
|
|
55
|
+
constructor(detail: string);
|
|
56
|
+
}
|
|
57
|
+
/** RFC 7636 §4.1: 43 chars of base64url from 32 random bytes. */
|
|
58
|
+
declare function generateCodeVerifier(): string;
|
|
59
|
+
/** RFC 7636 §4.2 S256: BASE64URL(SHA256(ASCII(verifier))). */
|
|
60
|
+
declare function codeChallengeFor(verifier: string): Promise<string>;
|
|
61
|
+
/** The `state` value: 256 random bits, ties the callback to this attempt. */
|
|
62
|
+
declare function generateState(): string;
|
|
63
|
+
/** Constant-time string comparison. */
|
|
64
|
+
declare function constantTimeEqual(a: string, b: string): boolean;
|
|
65
|
+
interface AuthorizeUrlInput {
|
|
66
|
+
authUrl: string;
|
|
67
|
+
clientId: string;
|
|
68
|
+
redirectUri: string;
|
|
69
|
+
state: string;
|
|
70
|
+
challenge: string;
|
|
71
|
+
scope: string;
|
|
72
|
+
}
|
|
73
|
+
declare function buildAuthorizeUrl(input: AuthorizeUrlInput): string;
|
|
74
|
+
/** Read `code` and `state` from the redirect the browser landed on; throws on error / missing parts. */
|
|
75
|
+
declare function parseCallbackUrl(callbackUrl: string): {
|
|
76
|
+
code: string;
|
|
77
|
+
returnedState: string;
|
|
78
|
+
};
|
|
79
|
+
interface AccessTokenClaims {
|
|
80
|
+
/** The person id (`sub`). */
|
|
81
|
+
personId: string;
|
|
82
|
+
email: string | null;
|
|
83
|
+
/** Expiry in ms since epoch, or null when the token carries no `exp`. */
|
|
84
|
+
expiresAtMs: number | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read `sub`, `email`, `exp` from an access token. The signature is NOT verified here: the token
|
|
88
|
+
* came straight from the issuer over TLS. Verify-on-receipt belongs to servers.
|
|
89
|
+
*/
|
|
90
|
+
declare function personIdFromAccessToken(token: string): AccessTokenClaims;
|
|
91
|
+
interface MatrxOAuthConfig {
|
|
92
|
+
/** Auth server origin, e.g. https://db.matrxserver.com */
|
|
93
|
+
authUrl: string;
|
|
94
|
+
/** This app's public PKCE client id. */
|
|
95
|
+
clientId: string;
|
|
96
|
+
redirectUri: string;
|
|
97
|
+
/** Default "email profile". Never include "openid". */
|
|
98
|
+
scope?: string;
|
|
99
|
+
/** Sent as `apikey` on token + logout calls when given. */
|
|
100
|
+
publishableKey?: string;
|
|
101
|
+
/** Refresh this long before expiry. Default 60_000. */
|
|
102
|
+
refreshLeadMs?: number;
|
|
103
|
+
/** A started sign-in expires after this. Default 600_000. */
|
|
104
|
+
attemptTtlMs?: number;
|
|
105
|
+
/** Token endpoint timeout. Default 15_000. */
|
|
106
|
+
requestTimeoutMs?: number;
|
|
107
|
+
/** Storage key namespace. Default "matrx-oauth". */
|
|
108
|
+
storageKeyPrefix?: string;
|
|
109
|
+
}
|
|
110
|
+
interface OpenAuthorizationContext {
|
|
111
|
+
state: string;
|
|
112
|
+
redirectUri: string;
|
|
113
|
+
}
|
|
114
|
+
interface MatrxOAuthPorts {
|
|
115
|
+
/** Where the session lives between runs (the package's SessionStorage shape). */
|
|
116
|
+
storage: SessionStorage;
|
|
117
|
+
/**
|
|
118
|
+
* Open the authorize URL and resolve with the full redirect URL the browser landed on.
|
|
119
|
+
* Reject if the person cancelled. Extension: chrome.identity.launchWebAuthFlow.
|
|
120
|
+
* Desktop: open the system browser and wait on the loopback server.
|
|
121
|
+
*/
|
|
122
|
+
openAuthorization(authorizeUrl: string, context: OpenAuthorizationContext): Promise<string>;
|
|
123
|
+
fetch?: typeof fetch;
|
|
124
|
+
now?: () => number;
|
|
125
|
+
/** Short-lived attempt state (verifier). Default: `storage`. Extension: chrome.storage.session. */
|
|
126
|
+
pendingStorage?: SessionStorage;
|
|
127
|
+
/** Serialises storage mutations across contexts (extension: navigator.locks). Default: in-process. */
|
|
128
|
+
lock?: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
129
|
+
/** Seal/unseal the refresh token at rest (extension: AES-GCM). Default: stored as-is. */
|
|
130
|
+
seal?: {
|
|
131
|
+
encrypt(plain: string): Promise<string>;
|
|
132
|
+
decrypt(sealed: string): Promise<string>;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Runs on freshly exchanged tokens BEFORE they displace a working session; throw to refuse
|
|
136
|
+
* (extension: GET /auth/v1/user). Never call per request.
|
|
137
|
+
*/
|
|
138
|
+
beforeCommit?: (accessToken: string) => Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
interface MatrxOAuthSession {
|
|
141
|
+
personId: string;
|
|
142
|
+
email: string | null;
|
|
143
|
+
accessToken: string;
|
|
144
|
+
expiresAtMs: number;
|
|
145
|
+
}
|
|
146
|
+
type MatrxOAuthEvent = {
|
|
147
|
+
type: "signed_in";
|
|
148
|
+
session: MatrxOAuthSession;
|
|
149
|
+
} | {
|
|
150
|
+
type: "refreshed";
|
|
151
|
+
session: MatrxOAuthSession;
|
|
152
|
+
} | {
|
|
153
|
+
type: "signed_out";
|
|
154
|
+
reason: "user" | "session_ended" | "client_changed" | "person_changed" | "unreadable";
|
|
155
|
+
};
|
|
156
|
+
interface TokenSet {
|
|
157
|
+
accessToken: string;
|
|
158
|
+
/** Null only if the server chose not to rotate it. */
|
|
159
|
+
refreshToken: string | null;
|
|
160
|
+
expiresAtMs: number;
|
|
161
|
+
}
|
|
162
|
+
interface MatrxOAuthClient {
|
|
163
|
+
/** Run the whole interactive sign-in. Resolves once tokens are stored. */
|
|
164
|
+
signIn(): Promise<MatrxOAuthSession>;
|
|
165
|
+
/** A usable access token, refreshing on expiry (single-flight). Null = signed out. Throws on transient failure with no valid token left. */
|
|
166
|
+
getAccessToken(): Promise<string | null>;
|
|
167
|
+
/** Force a refresh (alarms, 401 retry). Pass the token the server rejected to refuse adopting it. */
|
|
168
|
+
refresh(rejectedAccessToken?: string): Promise<string | null>;
|
|
169
|
+
/** The stored session without refreshing or touching the network. */
|
|
170
|
+
getSession(): Promise<MatrxOAuthSession | null>;
|
|
171
|
+
/** The person id from the stored access token, no network. Null = signed out. */
|
|
172
|
+
getPersonId(): Promise<string | null>;
|
|
173
|
+
/** Clear local state, then best-effort revoke on the server. Returns whether the server agreed (null = nothing to revoke). */
|
|
174
|
+
signOut(): Promise<{
|
|
175
|
+
revoked: boolean | null;
|
|
176
|
+
}>;
|
|
177
|
+
onChange(listener: (event: MatrxOAuthEvent) => void): () => void;
|
|
178
|
+
}
|
|
179
|
+
declare function createMatrxOAuth(config: MatrxOAuthConfig, ports: MatrxOAuthPorts): MatrxOAuthClient;
|
|
180
|
+
|
|
181
|
+
export { type AccessTokenClaims, type AuthorizeUrlInput, GrantRefusedError, type MatrxOAuthClient, type MatrxOAuthConfig, MatrxOAuthError, type MatrxOAuthErrorCode, type MatrxOAuthEvent, type MatrxOAuthPorts, type MatrxOAuthSession, type OpenAuthorizationContext, type TokenSet, TransientAuthError, buildAuthorizeUrl, codeChallengeFor, constantTimeEqual, createMatrxOAuth, generateCodeVerifier, generateState, parseCallbackUrl, personIdFromAccessToken };
|