@ticketlayer/backstage 3.1.0 → 4.1.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/README.md CHANGED
@@ -36,6 +36,173 @@ Every request carries `TL-Version` (the dated API version the client was
36
36
  generated against) and JSend responses are unwrapped to their `data`. Errors
37
37
  are `BackstageAPIError` with `code` and `statusCode`.
38
38
 
39
+ ### A 401 in bearer mode
40
+
41
+ Bearer mode has no default refresh endpoint. It used to POST `refreshToken`
42
+ to `/v1/auth/refresh`, which the API has removed; staff tokens are refreshed
43
+ at the identity issuer's `/oauth/token`, and `createAuth(...).createClient()`
44
+ (below) builds a client that does that by itself. On a 401 the client asks `onAuthFailure` if you gave one
45
+ (return `true` to retry the request, reading the token again), otherwise
46
+ POSTs `refreshToken` to `refreshEndpoint` if you set both (and hands the new
47
+ pair to `onTokenRefresh`), and otherwise throws `BackstageAPIError` with code
48
+ `SESSION_EXPIRED` without a second request. A host that rotates the token
49
+ behind an `accessToken` resolver needs none of these. Cookie mode still
50
+ defaults `refreshEndpoint` to the host app's own `/api/oauth/refresh`.
51
+
52
+ ## Signing in (first-party apps)
53
+
54
+ Ticketlayer's own apps (the Backstage shell, the packaged box office on the
55
+ desktop and on ChromeOS) sign a person in with one flow, in the shape of the
56
+ IETF draft "OAuth 2.0 for First-Party Applications": the app starts a sign-in
57
+ with its client id and a PKCE challenge, the person gives a password or an
58
+ emailed code (and a second factor when they have one), and the last step
59
+ answers a **one-time authorization code**, which the app redeems at the
60
+ issuer's `/oauth/token`. Tokens only ever come from the token endpoint, so
61
+ refresh rotation, revocation and DPoP binding work as for every other OAuth
62
+ client. The steps refuse any client that is not first party: a third party
63
+ uses the ordinary authorization code flow and the issuer's own pages.
64
+
65
+ The issuer is its own host, separate from the API (`https://auth.staging.t9r.dev`
66
+ beside `https://api.staging.t9r.dev`), so `createAuth` takes its own URL.
67
+
68
+ ```ts
69
+ import { createAuth } from '@ticketlayer/backstage';
70
+
71
+ const auth = createAuth({
72
+ issuerUrl: 'https://auth.staging.t9r.dev',
73
+ clientId: 'backstage-boxoffice-desktop', // or per call
74
+ });
75
+
76
+ const pkce = await auth.pkce(); // { verifier, challenge, method: 'S256' }
77
+ const dpop = await auth.createDpopKey(); // non-extractable ECDSA P-256 (WebCrypto)
78
+
79
+ const step = await auth.start({ email, codeChallenge: pkce.challenge, dpop });
80
+ // { method: 'password', alternatives: ['email_code'], authSession, expiresIn }
81
+
82
+ let result = await auth.password({ authSession: step.authSession, password });
83
+ // or: await auth.sendEmailCode({ authSession }); then auth.emailCode({ authSession, code })
84
+ // -> { status: 'code', code, redirectUri } | { status: 'mfa_required', authSession, factors }
85
+ if (result.status === 'mfa_required') {
86
+ result = await auth.mfa({ authSession: result.authSession, code: totp });
87
+ }
88
+
89
+ const tokens = await auth.exchange({
90
+ code: result.code,
91
+ codeVerifier: pkce.verifier,
92
+ redirectUri: result.redirectUri,
93
+ dpop, // binds the tokens to the key
94
+ });
95
+ // { accessToken, tokenType: 'DPoP', expiresIn, expiresAt, refreshToken, organisation, ... }
96
+ ```
97
+
98
+ - **`start`** answers the same whether or not the address has an account.
99
+ Pass `organisation: '<slug>'` to pin the first token to one organisation;
100
+ without it the token is pinned to the person's only organisation, or to
101
+ none when they have several. `method: 'sso'` with a `redirectUrl` is in the
102
+ type for when the issuer does home-realm discovery; it does not answer it
103
+ yet.
104
+ - **A 401 from a step is the answer** (`INVALID_CREDENTIALS`,
105
+ `INVALID_SECOND_FACTOR`, `INVALID_AUTH_SESSION` when the ten-minute sign-in
106
+ has expired), thrown as `BackstageAPIError` with that code; it is never
107
+ treated as an expired session. Too many attempts is `SIGN_IN_RATE_LIMITED`
108
+ (429).
109
+ - **Token endpoint errors** are `BackstageAPIError` with the OAuth error as
110
+ `code` (`invalid_grant`, `invalid_client`, ...).
111
+
112
+ ### A client that refreshes by itself
113
+
114
+ ```ts
115
+ const session = auth.createSession({
116
+ baseUrl: 'https://api.staging.t9r.dev',
117
+ tokens, // or just { refreshToken } from the keychain
118
+ dpop, // exactly when the tokens are DPoP-bound
119
+ onTokens: (t) => keychain.set('refresh', t.refreshToken),
120
+ onSessionEnded: () => showSignIn(),
121
+ });
122
+ const client = session.client; // or auth.createClient({...}) for the client alone
123
+
124
+ await client.events.list();
125
+ await session.selectOrganisation('acme-events'); // RFC 8693 token exchange; the client follows
126
+ ```
127
+
128
+ The client is in bearer mode. Its token provider refreshes through
129
+ `auth.refresh` ahead of expiry (30 seconds by default) and on a 401, one
130
+ refresh at a time however many requests are waiting, and hands every new
131
+ token set to `onTokens`: the refresh token rotates on every use, so keep the
132
+ newest. With `dpop` every API call goes as `Authorization: DPoP` with a
133
+ fresh proof, which is how the API accepts a bound token. `X-Ticketlayer-Org`
134
+ is the organisation the tokens are pinned to unless you pass
135
+ `organisationSlug`. When the issuer refuses the refresh token (revoked,
136
+ expired or reused), `onSessionEnded` is called once and requests fail with
137
+ `SESSION_EXPIRED`.
138
+
139
+ `auth.refresh({ refreshToken, dpop })` and
140
+ `auth.selectOrganisation({ slug, refreshToken, dpop })` are there to call
141
+ directly too.
142
+
143
+ ### DPoP
144
+
145
+ `createDpopKey()` makes an ECDSA P-256 pair whose private key is not
146
+ extractable: script can sign with it and never read it, so a token bound to it
147
+ is useless to whoever copies the token alone. Keep the `CryptoKeyPair` itself
148
+ (IndexedDB keeps it non-extractable) and pass it back as
149
+ `createDpopKey({ keyPair })` on the next launch. `dpopProof({ key, method, url,
150
+ accessToken?, nonce? })` makes one proof (`typ: dpop+jwt`, `alg: ES256`, the
151
+ public `jwk`; `htm`, `htu` without query or fragment, `iat`, a fresh `jti`,
152
+ and `ath` when an access token goes with it). A `use_dpop_nonce` answer from
153
+ the token endpoint is retried once with the nonce.
154
+
155
+ Where the password goes differs by host: an Electron app passes it over the
156
+ preload bridge and signs in from the main process; the isolated web app calls
157
+ the issuer itself (its origin is allowed on `/v1/auth/*` and `/oauth/token`
158
+ only). The shell's sign-in page calls the steps from the browser with
159
+ `createAuth({ ..., credentials: 'include' })`, so the issuer also leaves its
160
+ own login session, and redeems the code on its server.
161
+
162
+ ### The shell's server: `clientSecret`
163
+
164
+ `backstage-shell` is a confidential client. Its server redeems the code and
165
+ refreshes with its secret:
166
+
167
+ ```ts
168
+ const tokens = await auth.exchange({ code, codeVerifier, redirectUri, clientSecret: process.env.SHELL_CLIENT_SECRET });
169
+ ```
170
+
171
+ The secret goes as HTTP Basic (`client_secret_basic`). **It is for server code
172
+ only and must never ship to a browser**, an Electron renderer or anything else
173
+ a person can open: a public client has no secret and proves itself with PKCE
174
+ and DPoP. The SDK refuses a `clientSecret` wherever `window` and `document`
175
+ exist.
176
+
177
+ ### Nothing secret is logged
178
+
179
+ With `debug` on, the client prints `[redacted]` for the body and the answer of
180
+ every `/v1/auth/*` step, masks secret keys (`password`, `authSession`, tokens,
181
+ `client_secret`, `key`, `secret`) in every other body it logs, and the token
182
+ calls log only the grant type and the status. `test/auth-redaction.test.js`
183
+ runs a whole sign-in with every log on and fails if a password, code, handle,
184
+ verifier, secret or token appears.
185
+
186
+ ### A smoke run against a real issuer
187
+
188
+ `scripts/smoke-auth.mjs` drives `start`, `password` (and `mfa`), `exchange`
189
+ with a DPoP-bound code, one API call and a refresh against a running issuer,
190
+ printing outcomes and never a secret:
191
+
192
+ ```bash
193
+ npm run build
194
+ TL_ISSUER_URL=https://auth.staging.t9r.dev TL_API_URL=https://api.staging.t9r.dev \
195
+ TL_EMAIL=you@example.com TL_PASSWORD=... TL_ORG=your-org \
196
+ node scripts/smoke-auth.mjs
197
+ ```
198
+
199
+ Against a local API (`API_SURFACE=all`, `IDENTITY_ISSUER=http://127.0.0.1:3501`,
200
+ migrations applied and a person with a password in `identity_service.people`)
201
+ point `TL_ISSUER_URL` at it and leave `TL_API_URL` unset. Name `TL_ORG` when the
202
+ person belongs to more than one organisation: the token is otherwise pinned to
203
+ none, and the issuer does not yet tell a first-party client which
204
+ organisations there are to choose from.
205
+
39
206
  ## Develop
40
207
 
41
208
  ```bash
@@ -0,0 +1,27 @@
1
+ export interface DpopPublicJwk {
2
+ kty: 'EC';
3
+ crv: 'P-256';
4
+ x: string;
5
+ y: string;
6
+ }
7
+ export interface DpopProofInput {
8
+ method: string;
9
+ url: string;
10
+ accessToken?: string;
11
+ nonce?: string;
12
+ }
13
+ export interface DpopKey {
14
+ readonly keyPair: CryptoKeyPair;
15
+ readonly publicJwk: DpopPublicJwk;
16
+ readonly thumbprint: string;
17
+ proof(input: DpopProofInput): Promise<string>;
18
+ }
19
+ export declare function dpopHtu(url: string): string;
20
+ export declare function jwkThumbprint(jwk: DpopPublicJwk): Promise<string>;
21
+ export declare function createDpopKey(options?: {
22
+ keyPair?: CryptoKeyPair;
23
+ }): Promise<DpopKey>;
24
+ export declare function dpopProof(input: DpopProofInput & {
25
+ key: Pick<DpopKey, 'keyPair' | 'publicJwk'>;
26
+ }): Promise<string>;
27
+ //# sourceMappingURL=dpop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../../src/auth/dpop.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,OAAO,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,cAAc;IAE7B,MAAM,EAAE,MAAM,CAAC;IAEf,GAAG,EAAE,MAAM,CAAC;IAEZ,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IAEtB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAElC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/C;AAKD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG3C;AAGD,wBAAsB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAEvE;AAMD,wBAAsB,aAAa,CAAC,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,aAAa,CAAA;CAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAqB/F;AAQD,wBAAsB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,WAAW,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBxH"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dpopHtu = dpopHtu;
4
+ exports.jwkThumbprint = jwkThumbprint;
5
+ exports.createDpopKey = createDpopKey;
6
+ exports.dpopProof = dpopProof;
7
+ const encoding_1 = require("./encoding");
8
+ const ALGORITHM = { name: 'ECDSA', namedCurve: 'P-256' };
9
+ function dpopHtu(url) {
10
+ const parsed = new URL(url);
11
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
12
+ }
13
+ async function jwkThumbprint(jwk) {
14
+ return (0, encoding_1.base64url)(await (0, encoding_1.sha256)(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y })));
15
+ }
16
+ async function createDpopKey(options = {}) {
17
+ const subtle = (0, encoding_1.webCrypto)().subtle;
18
+ const keyPair = options.keyPair ?? (await subtle.generateKey(ALGORITHM, false, ['sign', 'verify']));
19
+ if (keyPair.privateKey.extractable) {
20
+ throw new Error('A DPoP private key must not be extractable');
21
+ }
22
+ const exported = await subtle.exportKey('jwk', keyPair.publicKey);
23
+ if (exported.kty !== 'EC' || exported.crv !== 'P-256' || !exported.x || !exported.y) {
24
+ throw new Error('A DPoP key must be an ECDSA P-256 key');
25
+ }
26
+ const publicJwk = { kty: 'EC', crv: 'P-256', x: exported.x, y: exported.y };
27
+ const thumbprint = await jwkThumbprint(publicJwk);
28
+ const key = {
29
+ keyPair,
30
+ publicJwk,
31
+ thumbprint,
32
+ proof: (input) => dpopProof({ key, ...input }),
33
+ };
34
+ return key;
35
+ }
36
+ async function dpopProof(input) {
37
+ const header = { typ: 'dpop+jwt', alg: 'ES256', jwk: input.key.publicJwk };
38
+ const payload = {
39
+ jti: (0, encoding_1.base64url)((0, encoding_1.randomBytes)(16)),
40
+ htm: input.method.toUpperCase(),
41
+ htu: dpopHtu(input.url),
42
+ iat: Math.floor(Date.now() / 1000),
43
+ };
44
+ if (input.accessToken)
45
+ payload.ath = (0, encoding_1.base64url)(await (0, encoding_1.sha256)(input.accessToken));
46
+ if (input.nonce)
47
+ payload.nonce = input.nonce;
48
+ const signingInput = `${(0, encoding_1.base64url)((0, encoding_1.utf8)(JSON.stringify(header)))}.${(0, encoding_1.base64url)((0, encoding_1.utf8)(JSON.stringify(payload)))}`;
49
+ const signature = await (0, encoding_1.webCrypto)().subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, input.key.keyPair.privateKey, (0, encoding_1.utf8)(signingInput));
50
+ return `${signingInput}.${(0, encoding_1.base64url)(signature)}`;
51
+ }
@@ -0,0 +1,8 @@
1
+ export declare function webCrypto(): Crypto;
2
+ export declare function base64url(bytes: ArrayBuffer | Uint8Array): string;
3
+ export declare function base64urlDecode(value: string): Uint8Array;
4
+ export declare const utf8: (value: string) => Uint8Array<ArrayBuffer>;
5
+ export declare function randomBytes(length: number): Uint8Array<ArrayBuffer>;
6
+ export declare function sha256(value: string): Promise<Uint8Array>;
7
+ export declare function decodeJwtPayload(jwt: string): Record<string, unknown> | undefined;
8
+ //# sourceMappingURL=encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../../src/auth/encoding.ts"],"names":[],"mappings":"AAOA,wBAAgB,SAAS,IAAI,MAAM,CAQlC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAKjE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,KAAG,UAAU,CAAC,WAAW,CAA+D,CAAC;AAE3H,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAInE;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAE/D;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CASjF"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.utf8 = void 0;
4
+ exports.webCrypto = webCrypto;
5
+ exports.base64url = base64url;
6
+ exports.base64urlDecode = base64urlDecode;
7
+ exports.randomBytes = randomBytes;
8
+ exports.sha256 = sha256;
9
+ exports.decodeJwtPayload = decodeJwtPayload;
10
+ function webCrypto() {
11
+ const c = globalThis.crypto;
12
+ if (!c || !c.subtle || typeof c.getRandomValues !== 'function') {
13
+ throw new Error('@ticketlayer/backstage auth needs WebCrypto (globalThis.crypto.subtle): a browser, or Node 20 or later');
14
+ }
15
+ return c;
16
+ }
17
+ function base64url(bytes) {
18
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
19
+ let binary = '';
20
+ for (let i = 0; i < view.length; i += 1)
21
+ binary += String.fromCharCode(view[i]);
22
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
23
+ }
24
+ function base64urlDecode(value) {
25
+ const padded = value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (value.length % 4)) % 4);
26
+ const binary = atob(padded);
27
+ const out = new Uint8Array(binary.length);
28
+ for (let i = 0; i < binary.length; i += 1)
29
+ out[i] = binary.charCodeAt(i);
30
+ return out;
31
+ }
32
+ const utf8 = (value) => new TextEncoder().encode(value);
33
+ exports.utf8 = utf8;
34
+ function randomBytes(length) {
35
+ const bytes = new Uint8Array(new ArrayBuffer(length));
36
+ webCrypto().getRandomValues(bytes);
37
+ return bytes;
38
+ }
39
+ async function sha256(value) {
40
+ return new Uint8Array(await webCrypto().subtle.digest('SHA-256', (0, exports.utf8)(value)));
41
+ }
42
+ function decodeJwtPayload(jwt) {
43
+ const part = jwt.split('.')[1];
44
+ if (!part)
45
+ return undefined;
46
+ try {
47
+ const parsed = JSON.parse(new TextDecoder().decode(base64urlDecode(part)));
48
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ }
@@ -0,0 +1,152 @@
1
+ import { BackstageAPIError, BackstageClient, type BackstageClientConfig, type DebugConfig } from '../client';
2
+ import { createDpopKey, dpopProof, type DpopKey } from './dpop';
3
+ import { pkce, pkceChallenge } from './pkce';
4
+ export { pkce, pkceChallenge, type PkcePair } from './pkce';
5
+ export { createDpopKey, dpopHtu, dpopProof, jwkThumbprint, type DpopKey, type DpopProofInput, type DpopPublicJwk, } from './dpop';
6
+ export declare const TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
7
+ export declare const REFRESH_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:refresh_token";
8
+ export declare const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
9
+ export interface AuthConfig {
10
+ issuerUrl: string;
11
+ clientId?: string;
12
+ tokenEndpoint?: string;
13
+ credentials?: RequestCredentials;
14
+ debug?: boolean | DebugConfig;
15
+ headers?: Record<string, string>;
16
+ }
17
+ export type SignInMethod = 'password' | 'email_code' | 'sso';
18
+ export type SecondFactor = 'totp' | 'email';
19
+ export interface StartInput {
20
+ email: string;
21
+ clientId?: string;
22
+ codeChallenge: string;
23
+ dpop?: Pick<DpopKey, 'thumbprint'>;
24
+ redirectUri?: string;
25
+ scope?: string;
26
+ organisation?: string;
27
+ }
28
+ export interface StartResult {
29
+ method: SignInMethod;
30
+ authSession: string;
31
+ alternatives: SignInMethod[];
32
+ expiresIn: number;
33
+ redirectUrl?: string;
34
+ }
35
+ export type StepResult = {
36
+ status: 'code';
37
+ code: string;
38
+ redirectUri: string;
39
+ } | {
40
+ status: 'mfa_required';
41
+ authSession: string;
42
+ factors: SecondFactor[];
43
+ };
44
+ export interface ClientCredentials {
45
+ clientId?: string;
46
+ clientSecret?: string;
47
+ dpop?: DpopKey;
48
+ }
49
+ export interface ExchangeInput extends ClientCredentials {
50
+ code: string;
51
+ codeVerifier: string;
52
+ redirectUri?: string;
53
+ }
54
+ export interface RefreshInput extends ClientCredentials {
55
+ refreshToken: string;
56
+ }
57
+ export interface SelectOrganisationInput extends ClientCredentials {
58
+ slug: string;
59
+ refreshToken?: string;
60
+ accessToken?: string;
61
+ }
62
+ export interface OrganisationRef {
63
+ slug: string;
64
+ name: string | null;
65
+ }
66
+ export interface TokenSet {
67
+ accessToken: string;
68
+ tokenType: string;
69
+ expiresIn: number;
70
+ expiresAt: number;
71
+ refreshToken?: string;
72
+ scope?: string;
73
+ idToken?: string;
74
+ organisation: OrganisationRef | null;
75
+ organisations?: {
76
+ id: string;
77
+ slug: string;
78
+ name: string;
79
+ }[];
80
+ }
81
+ export type StoredTokens = Partial<TokenSet> & {
82
+ accessToken?: string;
83
+ refreshToken?: string;
84
+ };
85
+ export interface SessionOptions extends Omit<BackstageClientConfig, 'authMode' | 'accessToken' | 'refreshToken' | 'onAuthFailure' | 'onTokenRefresh' | 'refreshEndpoint' | 'dpop'> {
86
+ tokens: StoredTokens;
87
+ clientId?: string;
88
+ clientSecret?: string;
89
+ dpop?: DpopKey;
90
+ onTokens?: (tokens: TokenSet) => void | Promise<void>;
91
+ onSessionEnded?: (error: BackstageAPIError) => void | Promise<void>;
92
+ refreshLeewaySeconds?: number;
93
+ }
94
+ export declare class BackstageAuth {
95
+ readonly issuerUrl: string;
96
+ readonly tokenEndpoint: string;
97
+ private readonly clientId?;
98
+ private readonly issuer;
99
+ private readonly logger;
100
+ private readonly debugConfig;
101
+ constructor(config: AuthConfig);
102
+ pkce: typeof pkce;
103
+ pkceChallenge: typeof pkceChallenge;
104
+ createDpopKey: typeof createDpopKey;
105
+ dpopProof: typeof dpopProof;
106
+ start(input: StartInput): Promise<StartResult>;
107
+ password(input: {
108
+ authSession: string;
109
+ password: string;
110
+ }): Promise<StepResult>;
111
+ sendEmailCode(input: {
112
+ authSession: string;
113
+ }): Promise<{
114
+ status: 'code_sent';
115
+ }>;
116
+ emailCode(input: {
117
+ authSession: string;
118
+ code: string;
119
+ }): Promise<StepResult>;
120
+ mfa(input: {
121
+ authSession: string;
122
+ code: string;
123
+ }): Promise<StepResult>;
124
+ private step;
125
+ exchange(input: ExchangeInput): Promise<TokenSet>;
126
+ refresh(input: RefreshInput): Promise<TokenSet>;
127
+ selectOrganisation(input: SelectOrganisationInput): Promise<TokenSet>;
128
+ private token;
129
+ private clientIdOf;
130
+ createSession(options: SessionOptions): AuthSession;
131
+ createClient(options: SessionOptions): BackstageClient;
132
+ }
133
+ export declare class AuthSession {
134
+ private readonly auth;
135
+ private readonly options;
136
+ readonly client: BackstageClient;
137
+ private current;
138
+ private pending?;
139
+ private ended;
140
+ private readonly leewayMs;
141
+ constructor(auth: BackstageAuth, options: SessionOptions);
142
+ get tokens(): StoredTokens;
143
+ get organisation(): OrganisationRef | null;
144
+ get hasEnded(): boolean;
145
+ refresh(): Promise<boolean>;
146
+ selectOrganisation(slug: string): Promise<TokenSet>;
147
+ private refreshAhead;
148
+ private runRefresh;
149
+ private adopt;
150
+ }
151
+ export declare function createAuth(config: AuthConfig): BackstageAuth;
152
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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"}