@canopy-io/browser 0.0.0-stage → 0.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +69 -2
- package/dist/index.cjs +505 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +190 -0
- package/dist/index.d.ts +190 -0
- package/dist/index.js +498 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/pkce.ts","../src/id-token.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Everything this package throws or hands back.\n *\n * Two classes, because an app acts on the distinction: `CanopyAuthError`\n * means Canopy answered and said no (an OAuth error from the redirect or the\n * token endpoint), and `CanopyConnectionError` means no answer could be read\n * at all, which in a browser is most often the page's origin missing from the\n * Environment's allowed web origins.\n */\n\n/**\n * The OAuth error codes that mean \"send the person through sign-in\": the\n * answers to `prompt=none` when there is no session to resume, or the sign-in\n * needs the person (a second factor, an enrolment, a policy).\n */\nconst NEEDS_SIGN_IN = new Set([\n \"login_required\",\n \"interaction_required\",\n \"consent_required\",\n]);\n\n/** Canopy answered with an OAuth error. Branch on `code`, never `message`. */\nexport class CanopyAuthError extends Error {\n override readonly name = \"CanopyAuthError\";\n /** The OAuth `error`, e.g. `login_required` or `invalid_grant`. */\n readonly code: string;\n /** The `error_description`, for logging. Not meant for end users. */\n readonly description: string | undefined;\n /** The HTTP status, when the error came from an endpoint rather than the redirect. */\n readonly status: number | undefined;\n\n constructor(code: string, description?: string, status?: number) {\n super(description ? `${code}: ${description}` : code);\n\n this.code = code;\n this.description = description;\n this.status = status;\n }\n\n /**\n * True when the answer means the person has to sign in: a silent attempt\n * found no session, or the session needs them for something. Show your\n * sign-in button rather than trying again silently.\n */\n get needsSignIn(): boolean {\n return NEEDS_SIGN_IN.has(this.code);\n }\n}\n\n/**\n * No response could be read from Canopy.\n *\n * A browser reports a response its cross-origin policy refuses exactly as it\n * reports a network failure, so this covers both. The message names the\n * likelier cause for a page that has never worked: its origin is not on the\n * Environment's allowed web origins.\n */\nexport class CanopyConnectionError extends Error {\n override readonly name = \"CanopyConnectionError\";\n /** The URL that could not be read. */\n readonly url: string;\n\n constructor(url: string, cause: unknown) {\n super(\n `Could not read a response from ${url}. If this page has never signed ` +\n `in, add its origin (${currentOrigin()}) to the Environment's ` +\n `Allowed web origins in the Developer Console; the browser blocks ` +\n `the response otherwise.`,\n { cause },\n );\n\n this.url = url;\n }\n}\n\nfunction currentOrigin(): string {\n return typeof location === \"undefined\" ? \"this origin\" : location.origin;\n}\n\nexport function isCanopyAuthError(error: unknown): error is CanopyAuthError {\n return error instanceof CanopyAuthError;\n}\n\nexport function isCanopyConnectionError(\n error: unknown,\n): error is CanopyConnectionError {\n return error instanceof CanopyConnectionError;\n}\n","/**\n * PKCE (RFC 7636) and the random values a sign-in needs, on the Web Crypto\n * API every supported browser has. S256 only: Canopy refuses `plain`.\n */\n\n/** URL-safe base64 without padding, as RFC 7636 and JWTs use it. */\nexport function base64UrlEncode(bytes: Uint8Array): string {\n let binary = \"\";\n\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function base64UrlDecode(value: string): string {\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(padded + \"=\".repeat((4 - (padded.length % 4)) % 4));\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n\n return new TextDecoder().decode(bytes);\n}\n\n/** A high-entropy random string: 32 bytes, 43 characters once encoded. */\nexport function randomString(): string {\n const bytes = new Uint8Array(32);\n\n crypto.getRandomValues(bytes);\n\n return base64UrlEncode(bytes);\n}\n\nexport interface PkcePair {\n verifier: string;\n challenge: string;\n}\n\nexport async function createPkcePair(): Promise<PkcePair> {\n const verifier = randomString();\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n\n return { verifier, challenge: base64UrlEncode(new Uint8Array(digest)) };\n}\n","import { CanopyAuthError } from \"./errors.js\";\nimport { base64UrlDecode } from \"./pkce.js\";\n\n/** The claims of a Canopy ID token. Present claims depend on the scopes asked for. */\nexport interface IdTokenClaims {\n /** The identity's id: the stable user identifier. */\n sub: string;\n iss: string;\n aud: string | string[];\n exp: number;\n iat: number;\n nonce?: string;\n sid?: string;\n /** With the `email` scope. */\n email?: string;\n email_verified?: boolean;\n /** With the `profile` scope. */\n name?: string;\n given_name?: string;\n family_name?: string;\n /** With the `org` scope. */\n account_id?: string;\n application_id?: string;\n environment_id?: string;\n /** When the Environment's organizations are on and the person belongs to one. */\n org_id?: string;\n org_roles?: string[];\n org_role_ids?: string[];\n [claim: string]: unknown;\n}\n\nexport function decodeJwtPayload(token: string): Record<string, unknown> {\n const payload = token.split(\".\")[1];\n\n if (!payload) {\n throw new CanopyAuthError(\"invalid_token\", \"The token is not a JWT.\");\n }\n\n const parsed: unknown = JSON.parse(base64UrlDecode(payload));\n\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new CanopyAuthError(\"invalid_token\", \"The token has no claims.\");\n }\n\n return parsed as Record<string, unknown>;\n}\n\n/**\n * Check an ID token received straight from the token endpoint.\n *\n * The signature is not checked here: OpenID Connect Core 3.1.3.7 lets a\n * client skip it for a token it received directly from the token endpoint\n * over TLS, which is the only way this package receives one. Everything that\n * ties the token to this sign-in is checked: the issuer, the audience, the\n * nonce this sign-in sent, and that it has not expired. A backend verifying a\n * token it was handed verifies the signature too (`@canopy-io/node`).\n */\nexport function checkIdToken(\n token: string,\n expected: { issuer: string; clientId: string; nonce: string | undefined },\n nowSeconds: number,\n): IdTokenClaims {\n const claims = decodeJwtPayload(token);\n const audience = claims[\"aud\"];\n const audiences = Array.isArray(audience) ? audience : [audience];\n\n if (claims[\"iss\"] !== expected.issuer) {\n throw new CanopyAuthError(\n \"invalid_token\",\n `The ID token's issuer is ${String(claims[\"iss\"])}, not ${expected.issuer}.`,\n );\n }\n\n if (!audiences.includes(expected.clientId)) {\n throw new CanopyAuthError(\n \"invalid_token\",\n \"The ID token was not issued to this client.\",\n );\n }\n\n if (expected.nonce !== undefined && claims[\"nonce\"] !== expected.nonce) {\n throw new CanopyAuthError(\n \"invalid_token\",\n \"The ID token answers another sign-in: its nonce does not match.\",\n );\n }\n\n if (typeof claims[\"exp\"] !== \"number\" || claims[\"exp\"] <= nowSeconds) {\n throw new CanopyAuthError(\"invalid_token\", \"The ID token has expired.\");\n }\n\n return claims as IdTokenClaims;\n}\n","import { CanopyAuthError, CanopyConnectionError } from \"./errors.js\";\nimport { checkIdToken, type IdTokenClaims } from \"./id-token.js\";\nimport { createPkcePair, randomString } from \"./pkce.js\";\n\n/** The storage this package keeps a sign-in's PKCE verifier in across the redirect. */\nexport type PendingStorage = Pick<\n Storage,\n \"getItem\" | \"setItem\" | \"removeItem\"\n>;\n\nexport interface CanopyBrowserClientOptions {\n /**\n * The Environment's issuer: `https://canopy-io.com/environments/<id>`, or\n * your custom auth domain. Endpoints are read from its discovery document.\n */\n issuer: string;\n /** The public OAuth client's `client_id`. It has no secret. */\n clientId: string;\n /** Registered on the client, exactly. Where Canopy sends the browser back. */\n redirectUri: string;\n /** Default `[\"openid\", \"profile\", \"email\"]`; each must be registered on the client. */\n scopes?: readonly string[];\n /** Registered on the client as a post-logout redirect URL. Where `signOut()` lands. */\n postLogoutRedirectUri?: string;\n /** Refresh the access token this many seconds before it expires. Default 60. */\n refreshLeewaySeconds?: number;\n /** Called once when the session ends on its own: its lifetime ran out, or it was revoked. */\n onSessionEnd?: (error: CanopyAuthError) => void;\n /** Defaults to `sessionStorage`. Holds only the verifier for the round trip, never a token. */\n storage?: PendingStorage;\n /** Defaults to `location.assign`. */\n navigate?: (url: string) => void;\n /** Defaults to removing the response parameters with `history.replaceState`. */\n replaceUrl?: (url: string) => void;\n /** Defaults to the global `fetch`. */\n fetch?: typeof globalThis.fetch;\n /** Defaults to `Date.now`. */\n now?: () => number;\n}\n\nexport interface SignInOptions {\n /** `login` shows the form whatever the browser holds; `none` shows no page. */\n prompt?: \"login\" | \"none\";\n /** Sign straight into this organization (its id). */\n organization?: string;\n /** An email address to put in the sign-in form. */\n loginHint?: string;\n /** Your app's path or URL to go back to after sign-in; returned by `handleRedirect()`. */\n returnTo?: string;\n}\n\n/** What `handleRedirect()` and `start()` found. */\nexport type SignInResult =\n /** Signed in: tokens are in memory. */\n | { status: \"signed-in\"; returnTo: string | undefined }\n /**\n * Canopy refused. `error.needsSignIn` means show your sign-in button: a\n * silent attempt found no session, or the sign-in needs the person.\n */\n | { status: \"error\"; error: CanopyAuthError; returnTo: string | undefined }\n /** The URL carried no answer from Canopy, and nobody is signed in. */\n | { status: \"signed-out\" }\n /** `start()` sent the browser to Canopy to resume a session silently. */\n | { status: \"redirecting\" };\n\ninterface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n revocation_endpoint?: string;\n end_session_endpoint?: string;\n}\n\ninterface TokenResponse {\n access_token: string;\n id_token?: string;\n refresh_token?: string;\n expires_in?: number;\n}\n\ninterface Pending {\n verifier: string;\n nonce: string;\n returnTo?: string;\n}\n\ninterface Session {\n accessToken: string;\n /** Epoch milliseconds. */\n expiresAt: number;\n refreshToken: string | undefined;\n idToken: string;\n claims: IdTokenClaims;\n}\n\nconst DEFAULT_SCOPES = [\"openid\", \"profile\", \"email\"] as const;\nconst RESPONSE_PARAMS = [\n \"code\",\n \"state\",\n \"error\",\n \"error_description\",\n \"error_uri\",\n \"iss\",\n];\n\n/**\n * Create a client for a single-page app signing in through Canopy's hosted\n * login as a public OAuth client.\n *\n * Tokens live in this object's memory and nowhere else, so a script injected\n * into the page cannot read them from storage. A reload loses them; `start()`\n * gets them back by sending the browser to Canopy with `prompt=none`, which\n * returns at once while the person's Canopy session lasts.\n */\nexport function createCanopyBrowserClient(\n options: CanopyBrowserClientOptions,\n): CanopyBrowserClient {\n return new CanopyBrowserClient(options);\n}\n\nexport class CanopyBrowserClient {\n readonly #issuer: string;\n readonly #clientId: string;\n readonly #redirectUri: string;\n readonly #scopes: readonly string[];\n readonly #postLogoutRedirectUri: string | undefined;\n readonly #leewayMs: number;\n readonly #onSessionEnd: ((error: CanopyAuthError) => void) | undefined;\n readonly #storage: PendingStorage;\n readonly #navigate: (url: string) => void;\n readonly #replaceUrl: (url: string) => void;\n readonly #fetch: typeof globalThis.fetch;\n readonly #now: () => number;\n\n #discovery: Promise<Discovery> | undefined;\n #session: Session | undefined;\n #refreshing: Promise<void> | undefined;\n\n constructor(options: CanopyBrowserClientOptions) {\n if (!options.issuer || !options.clientId || !options.redirectUri) {\n throw new TypeError(\n \"createCanopyBrowserClient needs an issuer, a clientId and a redirectUri.\",\n );\n }\n\n this.#issuer = withoutTrailingSlashes(options.issuer);\n this.#clientId = options.clientId;\n this.#redirectUri = options.redirectUri;\n this.#scopes = options.scopes ?? DEFAULT_SCOPES;\n this.#postLogoutRedirectUri = options.postLogoutRedirectUri;\n this.#leewayMs = (options.refreshLeewaySeconds ?? 60) * 1000;\n this.#onSessionEnd = options.onSessionEnd;\n this.#storage = options.storage ?? sessionStorage;\n this.#navigate = options.navigate ?? ((url) => location.assign(url));\n this.#replaceUrl =\n options.replaceUrl ??\n ((url) => history.replaceState(history.state, \"\", url));\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.#now = options.now ?? Date.now;\n }\n\n /**\n * Run once when the app loads. Finishes a sign-in when Canopy has just sent\n * the browser back, and otherwise tries to resume Canopy's session without\n * showing a page. Show your sign-in button on `signed-out`, and on `error`\n * when `error.needsSignIn`.\n */\n async start(url: string = location.href): Promise<SignInResult> {\n if (hasResponse(url)) {\n return this.handleRedirect(url);\n }\n\n if (this.#session) {\n return { status: \"signed-in\", returnTo: undefined };\n }\n\n await this.signIn({ prompt: \"none\" });\n\n return { status: \"redirecting\" };\n }\n\n /** Send the browser to Canopy's sign-in. */\n async signIn(signIn: SignInOptions = {}): Promise<void> {\n const discovery = await this.#discover();\n const { verifier, challenge } = await createPkcePair();\n const state = randomString();\n const nonce = randomString();\n const pending: Pending = { verifier, nonce };\n\n if (signIn.returnTo !== undefined) {\n pending.returnTo = signIn.returnTo;\n }\n\n this.#storage.setItem(this.#pendingKey(state), JSON.stringify(pending));\n\n const authorize = new URL(discovery.authorization_endpoint);\n\n authorize.search = new URLSearchParams({\n response_type: \"code\",\n client_id: this.#clientId,\n redirect_uri: this.#redirectUri,\n scope: this.#scopes.join(\" \"),\n state,\n nonce,\n code_challenge: challenge,\n code_challenge_method: \"S256\",\n ...(signIn.prompt ? { prompt: signIn.prompt } : {}),\n ...(signIn.organization ? { organization: signIn.organization } : {}),\n ...(signIn.loginHint ? { login_hint: signIn.loginHint } : {}),\n }).toString();\n\n this.#navigate(authorize.toString());\n }\n\n /**\n * Finish the sign-in Canopy just sent the browser back from: check `state`\n * against this browser's own request, exchange the code with the client id\n * and the PKCE verifier, and take the response parameters out of the URL.\n */\n async handleRedirect(url: string = location.href): Promise<SignInResult> {\n const current = new URL(url);\n const params = current.searchParams;\n const state = params.get(\"state\");\n\n if (!hasResponse(url)) {\n return { status: \"signed-out\" };\n }\n\n this.#replaceUrl(withoutResponse(current));\n\n const pending = state ? this.#takePending(state) : undefined;\n\n if (!pending) {\n return {\n status: \"error\",\n error: new CanopyAuthError(\n \"state_mismatch\",\n \"The response does not answer a sign-in this browser started.\",\n ),\n returnTo: undefined,\n };\n }\n\n const error = params.get(\"error\");\n\n if (error) {\n return {\n status: \"error\",\n error: new CanopyAuthError(\n error,\n params.get(\"error_description\") ?? undefined,\n ),\n returnTo: pending.returnTo,\n };\n }\n\n try {\n const tokens = await this.#token({\n grant_type: \"authorization_code\",\n code: params.get(\"code\") ?? \"\",\n redirect_uri: this.#redirectUri,\n code_verifier: pending.verifier,\n });\n\n this.#keep(tokens, pending.nonce);\n\n return { status: \"signed-in\", returnTo: pending.returnTo };\n } catch (caught) {\n if (caught instanceof CanopyAuthError) {\n return { status: \"error\", error: caught, returnTo: pending.returnTo };\n }\n\n throw caught;\n }\n }\n\n /** Whether tokens are in memory. */\n isSignedIn(): boolean {\n return this.#session !== undefined;\n }\n\n /**\n * A current access token for `Authorization: Bearer`, refreshed first when\n * it is about to expire. `null` when nobody is signed in, or the session has\n * ended (its lifetime ran out or it was revoked).\n */\n async getAccessToken(): Promise<string | null> {\n if (!this.#session) {\n return null;\n }\n\n if (this.#session.expiresAt - this.#leewayMs <= this.#now()) {\n try {\n await this.refresh();\n } catch (caught) {\n if (caught instanceof CanopyAuthError) {\n return null;\n }\n\n throw caught;\n }\n }\n\n return this.#session?.accessToken ?? null;\n }\n\n /**\n * Trade the refresh token for new tokens now. Concurrent calls share one\n * request, so the rotated token is never presented twice from this page.\n * A refused refresh ends the session in memory and calls `onSessionEnd`.\n */\n refresh(): Promise<void> {\n this.#refreshing ??= this.#refresh().finally(() => {\n this.#refreshing = undefined;\n });\n\n return this.#refreshing;\n }\n\n /** The signed-in person's ID token claims, or `null`. */\n getClaims(): IdTokenClaims | null {\n return this.#session?.claims ?? null;\n }\n\n /**\n * Sign out of the app and of Canopy: revoke the refresh token, forget the\n * tokens, then send the browser to Canopy's end-session endpoint with the\n * ID token as the hint. Skipping the second half would let the next\n * `start()` sign the person straight back in.\n */\n async signOut(): Promise<void> {\n const session = this.#session;\n const discovery = await this.#discover();\n\n this.#session = undefined;\n\n if (session?.refreshToken && discovery.revocation_endpoint) {\n try {\n await this.#post(discovery.revocation_endpoint, {\n token: session.refreshToken,\n token_type_hint: \"refresh_token\",\n });\n } catch {\n // Ending the session at Canopy below revokes this sign-in's refresh\n // tokens too, so a failed revocation is not worth stopping for.\n }\n }\n\n if (!discovery.end_session_endpoint) {\n return;\n }\n\n const endSession = new URL(discovery.end_session_endpoint);\n const query = new URLSearchParams({ client_id: this.#clientId });\n\n if (session) {\n query.set(\"id_token_hint\", session.idToken);\n }\n\n if (this.#postLogoutRedirectUri) {\n query.set(\"post_logout_redirect_uri\", this.#postLogoutRedirectUri);\n }\n\n endSession.search = query.toString();\n this.#navigate(endSession.toString());\n }\n\n async #refresh(): Promise<void> {\n const session = this.#session;\n\n if (!session?.refreshToken) {\n throw new CanopyAuthError(\n \"invalid_grant\",\n \"There is no refresh token to use.\",\n );\n }\n\n try {\n const tokens = await this.#token({\n grant_type: \"refresh_token\",\n refresh_token: session.refreshToken,\n });\n\n this.#keep(tokens, undefined, session);\n } catch (caught) {\n if (caught instanceof CanopyAuthError) {\n this.#session = undefined;\n this.#onSessionEnd?.(caught);\n }\n\n throw caught;\n }\n }\n\n #keep(\n tokens: TokenResponse,\n nonce: string | undefined,\n previous?: Session,\n ): void {\n const idToken = tokens.id_token ?? previous?.idToken;\n\n if (!idToken) {\n throw new CanopyAuthError(\n \"invalid_token\",\n \"The token response carried no ID token. Ask for the openid scope.\",\n );\n }\n\n const claims = tokens.id_token\n ? checkIdToken(\n tokens.id_token,\n { issuer: this.#issuer, clientId: this.#clientId, nonce },\n Math.floor(this.#now() / 1000),\n )\n : (previous as Session).claims;\n\n this.#session = {\n accessToken: tokens.access_token,\n expiresAt: this.#now() + (tokens.expires_in ?? 0) * 1000,\n refreshToken: tokens.refresh_token ?? previous?.refreshToken,\n idToken,\n claims,\n };\n }\n\n async #token(body: Record<string, string>): Promise<TokenResponse> {\n const discovery = await this.#discover();\n\n return (await this.#post(discovery.token_endpoint, body)) as TokenResponse;\n }\n\n async #post(url: string, body: Record<string, string>): Promise<unknown> {\n let response: Response;\n\n try {\n response = await this.#fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({ ...body, client_id: this.#clientId }),\n });\n } catch (caught) {\n throw new CanopyConnectionError(url, caught);\n }\n\n const text = await response.text();\n const parsed: unknown = text ? JSON.parse(text) : {};\n\n if (!response.ok) {\n const failure = parsed as { error?: string; error_description?: string };\n\n throw new CanopyAuthError(\n failure.error ?? \"server_error\",\n failure.error_description,\n response.status,\n );\n }\n\n return parsed;\n }\n\n #discover(): Promise<Discovery> {\n this.#discovery ??= this.#readDiscovery().catch((caught: unknown) => {\n this.#discovery = undefined;\n\n throw caught;\n });\n\n return this.#discovery;\n }\n\n async #readDiscovery(): Promise<Discovery> {\n const url = `${this.#issuer}/.well-known/openid-configuration`;\n let response: Response;\n\n try {\n response = await this.#fetch(url);\n } catch (caught) {\n throw new CanopyConnectionError(url, caught);\n }\n\n if (!response.ok) {\n throw new CanopyAuthError(\n \"invalid_issuer\",\n `${url} answered ${response.status}. Check the issuer.`,\n response.status,\n );\n }\n\n const discovery = (await response.json()) as Discovery;\n\n // OpenID Connect Discovery 4.3: the document must name the issuer it was\n // fetched for, or its endpoints belong to someone else.\n if (discovery.issuer !== this.#issuer) {\n throw new CanopyAuthError(\n \"invalid_issuer\",\n `The discovery document names ${discovery.issuer}, not ${this.#issuer}.`,\n );\n }\n\n return discovery;\n }\n\n #pendingKey(state: string): string {\n return `canopy.browser.${this.#clientId}.${state}`;\n }\n\n #takePending(state: string): Pending | undefined {\n const key = this.#pendingKey(state);\n const raw = this.#storage.getItem(key);\n\n this.#storage.removeItem(key);\n\n return raw ? (JSON.parse(raw) as Pending) : undefined;\n }\n}\n\n/**\n * The issuer with any trailing slashes removed, so `…/env-1/` and `…/env-1`\n * name the same issuer. A loop rather than a regular expression: the value\n * is the caller's, and an anchored `/+$` backtracks on a long run of slashes.\n */\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length;\n\n while (end > 0 && value[end - 1] === \"/\") {\n end -= 1;\n }\n\n return value.slice(0, end);\n}\n\nfunction hasResponse(url: string): boolean {\n const params = new URL(url).searchParams;\n\n return params.has(\"state\") && (params.has(\"code\") || params.has(\"error\"));\n}\n\nfunction withoutResponse(url: URL): string {\n const clean = new URL(url.toString());\n\n for (const name of RESPONSE_PARAMS) {\n clean.searchParams.delete(name);\n }\n\n return clean.toString();\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything this package throws or hands back.
|
|
3
|
+
*
|
|
4
|
+
* Two classes, because an app acts on the distinction: `CanopyAuthError`
|
|
5
|
+
* means Canopy answered and said no (an OAuth error from the redirect or the
|
|
6
|
+
* token endpoint), and `CanopyConnectionError` means no answer could be read
|
|
7
|
+
* at all, which in a browser is most often the page's origin missing from the
|
|
8
|
+
* Environment's allowed web origins.
|
|
9
|
+
*/
|
|
10
|
+
/** Canopy answered with an OAuth error. Branch on `code`, never `message`. */
|
|
11
|
+
declare class CanopyAuthError extends Error {
|
|
12
|
+
readonly name = "CanopyAuthError";
|
|
13
|
+
/** The OAuth `error`, e.g. `login_required` or `invalid_grant`. */
|
|
14
|
+
readonly code: string;
|
|
15
|
+
/** The `error_description`, for logging. Not meant for end users. */
|
|
16
|
+
readonly description: string | undefined;
|
|
17
|
+
/** The HTTP status, when the error came from an endpoint rather than the redirect. */
|
|
18
|
+
readonly status: number | undefined;
|
|
19
|
+
constructor(code: string, description?: string, status?: number);
|
|
20
|
+
/**
|
|
21
|
+
* True when the answer means the person has to sign in: a silent attempt
|
|
22
|
+
* found no session, or the session needs them for something. Show your
|
|
23
|
+
* sign-in button rather than trying again silently.
|
|
24
|
+
*/
|
|
25
|
+
get needsSignIn(): boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* No response could be read from Canopy.
|
|
29
|
+
*
|
|
30
|
+
* A browser reports a response its cross-origin policy refuses exactly as it
|
|
31
|
+
* reports a network failure, so this covers both. The message names the
|
|
32
|
+
* likelier cause for a page that has never worked: its origin is not on the
|
|
33
|
+
* Environment's allowed web origins.
|
|
34
|
+
*/
|
|
35
|
+
declare class CanopyConnectionError extends Error {
|
|
36
|
+
readonly name = "CanopyConnectionError";
|
|
37
|
+
/** The URL that could not be read. */
|
|
38
|
+
readonly url: string;
|
|
39
|
+
constructor(url: string, cause: unknown);
|
|
40
|
+
}
|
|
41
|
+
declare function isCanopyAuthError(error: unknown): error is CanopyAuthError;
|
|
42
|
+
declare function isCanopyConnectionError(error: unknown): error is CanopyConnectionError;
|
|
43
|
+
|
|
44
|
+
/** The claims of a Canopy ID token. Present claims depend on the scopes asked for. */
|
|
45
|
+
interface IdTokenClaims {
|
|
46
|
+
/** The identity's id: the stable user identifier. */
|
|
47
|
+
sub: string;
|
|
48
|
+
iss: string;
|
|
49
|
+
aud: string | string[];
|
|
50
|
+
exp: number;
|
|
51
|
+
iat: number;
|
|
52
|
+
nonce?: string;
|
|
53
|
+
sid?: string;
|
|
54
|
+
/** With the `email` scope. */
|
|
55
|
+
email?: string;
|
|
56
|
+
email_verified?: boolean;
|
|
57
|
+
/** With the `profile` scope. */
|
|
58
|
+
name?: string;
|
|
59
|
+
given_name?: string;
|
|
60
|
+
family_name?: string;
|
|
61
|
+
/** With the `org` scope. */
|
|
62
|
+
account_id?: string;
|
|
63
|
+
application_id?: string;
|
|
64
|
+
environment_id?: string;
|
|
65
|
+
/** When the Environment's organizations are on and the person belongs to one. */
|
|
66
|
+
org_id?: string;
|
|
67
|
+
org_roles?: string[];
|
|
68
|
+
org_role_ids?: string[];
|
|
69
|
+
[claim: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The storage this package keeps a sign-in's PKCE verifier in across the redirect. */
|
|
73
|
+
type PendingStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
74
|
+
interface CanopyBrowserClientOptions {
|
|
75
|
+
/**
|
|
76
|
+
* The Environment's issuer: `https://canopy-io.com/environments/<id>`, or
|
|
77
|
+
* your custom auth domain. Endpoints are read from its discovery document.
|
|
78
|
+
*/
|
|
79
|
+
issuer: string;
|
|
80
|
+
/** The public OAuth client's `client_id`. It has no secret. */
|
|
81
|
+
clientId: string;
|
|
82
|
+
/** Registered on the client, exactly. Where Canopy sends the browser back. */
|
|
83
|
+
redirectUri: string;
|
|
84
|
+
/** Default `["openid", "profile", "email"]`; each must be registered on the client. */
|
|
85
|
+
scopes?: readonly string[];
|
|
86
|
+
/** Registered on the client as a post-logout redirect URL. Where `signOut()` lands. */
|
|
87
|
+
postLogoutRedirectUri?: string;
|
|
88
|
+
/** Refresh the access token this many seconds before it expires. Default 60. */
|
|
89
|
+
refreshLeewaySeconds?: number;
|
|
90
|
+
/** Called once when the session ends on its own: its lifetime ran out, or it was revoked. */
|
|
91
|
+
onSessionEnd?: (error: CanopyAuthError) => void;
|
|
92
|
+
/** Defaults to `sessionStorage`. Holds only the verifier for the round trip, never a token. */
|
|
93
|
+
storage?: PendingStorage;
|
|
94
|
+
/** Defaults to `location.assign`. */
|
|
95
|
+
navigate?: (url: string) => void;
|
|
96
|
+
/** Defaults to removing the response parameters with `history.replaceState`. */
|
|
97
|
+
replaceUrl?: (url: string) => void;
|
|
98
|
+
/** Defaults to the global `fetch`. */
|
|
99
|
+
fetch?: typeof globalThis.fetch;
|
|
100
|
+
/** Defaults to `Date.now`. */
|
|
101
|
+
now?: () => number;
|
|
102
|
+
}
|
|
103
|
+
interface SignInOptions {
|
|
104
|
+
/** `login` shows the form whatever the browser holds; `none` shows no page. */
|
|
105
|
+
prompt?: "login" | "none";
|
|
106
|
+
/** Sign straight into this organization (its id). */
|
|
107
|
+
organization?: string;
|
|
108
|
+
/** An email address to put in the sign-in form. */
|
|
109
|
+
loginHint?: string;
|
|
110
|
+
/** Your app's path or URL to go back to after sign-in; returned by `handleRedirect()`. */
|
|
111
|
+
returnTo?: string;
|
|
112
|
+
}
|
|
113
|
+
/** What `handleRedirect()` and `start()` found. */
|
|
114
|
+
type SignInResult =
|
|
115
|
+
/** Signed in: tokens are in memory. */
|
|
116
|
+
{
|
|
117
|
+
status: "signed-in";
|
|
118
|
+
returnTo: string | undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Canopy refused. `error.needsSignIn` means show your sign-in button: a
|
|
122
|
+
* silent attempt found no session, or the sign-in needs the person.
|
|
123
|
+
*/
|
|
124
|
+
| {
|
|
125
|
+
status: "error";
|
|
126
|
+
error: CanopyAuthError;
|
|
127
|
+
returnTo: string | undefined;
|
|
128
|
+
}
|
|
129
|
+
/** The URL carried no answer from Canopy, and nobody is signed in. */
|
|
130
|
+
| {
|
|
131
|
+
status: "signed-out";
|
|
132
|
+
}
|
|
133
|
+
/** `start()` sent the browser to Canopy to resume a session silently. */
|
|
134
|
+
| {
|
|
135
|
+
status: "redirecting";
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Create a client for a single-page app signing in through Canopy's hosted
|
|
139
|
+
* login as a public OAuth client.
|
|
140
|
+
*
|
|
141
|
+
* Tokens live in this object's memory and nowhere else, so a script injected
|
|
142
|
+
* into the page cannot read them from storage. A reload loses them; `start()`
|
|
143
|
+
* gets them back by sending the browser to Canopy with `prompt=none`, which
|
|
144
|
+
* returns at once while the person's Canopy session lasts.
|
|
145
|
+
*/
|
|
146
|
+
declare function createCanopyBrowserClient(options: CanopyBrowserClientOptions): CanopyBrowserClient;
|
|
147
|
+
declare class CanopyBrowserClient {
|
|
148
|
+
#private;
|
|
149
|
+
constructor(options: CanopyBrowserClientOptions);
|
|
150
|
+
/**
|
|
151
|
+
* Run once when the app loads. Finishes a sign-in when Canopy has just sent
|
|
152
|
+
* the browser back, and otherwise tries to resume Canopy's session without
|
|
153
|
+
* showing a page. Show your sign-in button on `signed-out`, and on `error`
|
|
154
|
+
* when `error.needsSignIn`.
|
|
155
|
+
*/
|
|
156
|
+
start(url?: string): Promise<SignInResult>;
|
|
157
|
+
/** Send the browser to Canopy's sign-in. */
|
|
158
|
+
signIn(signIn?: SignInOptions): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Finish the sign-in Canopy just sent the browser back from: check `state`
|
|
161
|
+
* against this browser's own request, exchange the code with the client id
|
|
162
|
+
* and the PKCE verifier, and take the response parameters out of the URL.
|
|
163
|
+
*/
|
|
164
|
+
handleRedirect(url?: string): Promise<SignInResult>;
|
|
165
|
+
/** Whether tokens are in memory. */
|
|
166
|
+
isSignedIn(): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* A current access token for `Authorization: Bearer`, refreshed first when
|
|
169
|
+
* it is about to expire. `null` when nobody is signed in, or the session has
|
|
170
|
+
* ended (its lifetime ran out or it was revoked).
|
|
171
|
+
*/
|
|
172
|
+
getAccessToken(): Promise<string | null>;
|
|
173
|
+
/**
|
|
174
|
+
* Trade the refresh token for new tokens now. Concurrent calls share one
|
|
175
|
+
* request, so the rotated token is never presented twice from this page.
|
|
176
|
+
* A refused refresh ends the session in memory and calls `onSessionEnd`.
|
|
177
|
+
*/
|
|
178
|
+
refresh(): Promise<void>;
|
|
179
|
+
/** The signed-in person's ID token claims, or `null`. */
|
|
180
|
+
getClaims(): IdTokenClaims | null;
|
|
181
|
+
/**
|
|
182
|
+
* Sign out of the app and of Canopy: revoke the refresh token, forget the
|
|
183
|
+
* tokens, then send the browser to Canopy's end-session endpoint with the
|
|
184
|
+
* ID token as the hint. Skipping the second half would let the next
|
|
185
|
+
* `start()` sign the person straight back in.
|
|
186
|
+
*/
|
|
187
|
+
signOut(): Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { CanopyAuthError, CanopyBrowserClient, type CanopyBrowserClientOptions, CanopyConnectionError, type IdTokenClaims, type PendingStorage, type SignInOptions, type SignInResult, createCanopyBrowserClient, isCanopyAuthError, isCanopyConnectionError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything this package throws or hands back.
|
|
3
|
+
*
|
|
4
|
+
* Two classes, because an app acts on the distinction: `CanopyAuthError`
|
|
5
|
+
* means Canopy answered and said no (an OAuth error from the redirect or the
|
|
6
|
+
* token endpoint), and `CanopyConnectionError` means no answer could be read
|
|
7
|
+
* at all, which in a browser is most often the page's origin missing from the
|
|
8
|
+
* Environment's allowed web origins.
|
|
9
|
+
*/
|
|
10
|
+
/** Canopy answered with an OAuth error. Branch on `code`, never `message`. */
|
|
11
|
+
declare class CanopyAuthError extends Error {
|
|
12
|
+
readonly name = "CanopyAuthError";
|
|
13
|
+
/** The OAuth `error`, e.g. `login_required` or `invalid_grant`. */
|
|
14
|
+
readonly code: string;
|
|
15
|
+
/** The `error_description`, for logging. Not meant for end users. */
|
|
16
|
+
readonly description: string | undefined;
|
|
17
|
+
/** The HTTP status, when the error came from an endpoint rather than the redirect. */
|
|
18
|
+
readonly status: number | undefined;
|
|
19
|
+
constructor(code: string, description?: string, status?: number);
|
|
20
|
+
/**
|
|
21
|
+
* True when the answer means the person has to sign in: a silent attempt
|
|
22
|
+
* found no session, or the session needs them for something. Show your
|
|
23
|
+
* sign-in button rather than trying again silently.
|
|
24
|
+
*/
|
|
25
|
+
get needsSignIn(): boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* No response could be read from Canopy.
|
|
29
|
+
*
|
|
30
|
+
* A browser reports a response its cross-origin policy refuses exactly as it
|
|
31
|
+
* reports a network failure, so this covers both. The message names the
|
|
32
|
+
* likelier cause for a page that has never worked: its origin is not on the
|
|
33
|
+
* Environment's allowed web origins.
|
|
34
|
+
*/
|
|
35
|
+
declare class CanopyConnectionError extends Error {
|
|
36
|
+
readonly name = "CanopyConnectionError";
|
|
37
|
+
/** The URL that could not be read. */
|
|
38
|
+
readonly url: string;
|
|
39
|
+
constructor(url: string, cause: unknown);
|
|
40
|
+
}
|
|
41
|
+
declare function isCanopyAuthError(error: unknown): error is CanopyAuthError;
|
|
42
|
+
declare function isCanopyConnectionError(error: unknown): error is CanopyConnectionError;
|
|
43
|
+
|
|
44
|
+
/** The claims of a Canopy ID token. Present claims depend on the scopes asked for. */
|
|
45
|
+
interface IdTokenClaims {
|
|
46
|
+
/** The identity's id: the stable user identifier. */
|
|
47
|
+
sub: string;
|
|
48
|
+
iss: string;
|
|
49
|
+
aud: string | string[];
|
|
50
|
+
exp: number;
|
|
51
|
+
iat: number;
|
|
52
|
+
nonce?: string;
|
|
53
|
+
sid?: string;
|
|
54
|
+
/** With the `email` scope. */
|
|
55
|
+
email?: string;
|
|
56
|
+
email_verified?: boolean;
|
|
57
|
+
/** With the `profile` scope. */
|
|
58
|
+
name?: string;
|
|
59
|
+
given_name?: string;
|
|
60
|
+
family_name?: string;
|
|
61
|
+
/** With the `org` scope. */
|
|
62
|
+
account_id?: string;
|
|
63
|
+
application_id?: string;
|
|
64
|
+
environment_id?: string;
|
|
65
|
+
/** When the Environment's organizations are on and the person belongs to one. */
|
|
66
|
+
org_id?: string;
|
|
67
|
+
org_roles?: string[];
|
|
68
|
+
org_role_ids?: string[];
|
|
69
|
+
[claim: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The storage this package keeps a sign-in's PKCE verifier in across the redirect. */
|
|
73
|
+
type PendingStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
74
|
+
interface CanopyBrowserClientOptions {
|
|
75
|
+
/**
|
|
76
|
+
* The Environment's issuer: `https://canopy-io.com/environments/<id>`, or
|
|
77
|
+
* your custom auth domain. Endpoints are read from its discovery document.
|
|
78
|
+
*/
|
|
79
|
+
issuer: string;
|
|
80
|
+
/** The public OAuth client's `client_id`. It has no secret. */
|
|
81
|
+
clientId: string;
|
|
82
|
+
/** Registered on the client, exactly. Where Canopy sends the browser back. */
|
|
83
|
+
redirectUri: string;
|
|
84
|
+
/** Default `["openid", "profile", "email"]`; each must be registered on the client. */
|
|
85
|
+
scopes?: readonly string[];
|
|
86
|
+
/** Registered on the client as a post-logout redirect URL. Where `signOut()` lands. */
|
|
87
|
+
postLogoutRedirectUri?: string;
|
|
88
|
+
/** Refresh the access token this many seconds before it expires. Default 60. */
|
|
89
|
+
refreshLeewaySeconds?: number;
|
|
90
|
+
/** Called once when the session ends on its own: its lifetime ran out, or it was revoked. */
|
|
91
|
+
onSessionEnd?: (error: CanopyAuthError) => void;
|
|
92
|
+
/** Defaults to `sessionStorage`. Holds only the verifier for the round trip, never a token. */
|
|
93
|
+
storage?: PendingStorage;
|
|
94
|
+
/** Defaults to `location.assign`. */
|
|
95
|
+
navigate?: (url: string) => void;
|
|
96
|
+
/** Defaults to removing the response parameters with `history.replaceState`. */
|
|
97
|
+
replaceUrl?: (url: string) => void;
|
|
98
|
+
/** Defaults to the global `fetch`. */
|
|
99
|
+
fetch?: typeof globalThis.fetch;
|
|
100
|
+
/** Defaults to `Date.now`. */
|
|
101
|
+
now?: () => number;
|
|
102
|
+
}
|
|
103
|
+
interface SignInOptions {
|
|
104
|
+
/** `login` shows the form whatever the browser holds; `none` shows no page. */
|
|
105
|
+
prompt?: "login" | "none";
|
|
106
|
+
/** Sign straight into this organization (its id). */
|
|
107
|
+
organization?: string;
|
|
108
|
+
/** An email address to put in the sign-in form. */
|
|
109
|
+
loginHint?: string;
|
|
110
|
+
/** Your app's path or URL to go back to after sign-in; returned by `handleRedirect()`. */
|
|
111
|
+
returnTo?: string;
|
|
112
|
+
}
|
|
113
|
+
/** What `handleRedirect()` and `start()` found. */
|
|
114
|
+
type SignInResult =
|
|
115
|
+
/** Signed in: tokens are in memory. */
|
|
116
|
+
{
|
|
117
|
+
status: "signed-in";
|
|
118
|
+
returnTo: string | undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Canopy refused. `error.needsSignIn` means show your sign-in button: a
|
|
122
|
+
* silent attempt found no session, or the sign-in needs the person.
|
|
123
|
+
*/
|
|
124
|
+
| {
|
|
125
|
+
status: "error";
|
|
126
|
+
error: CanopyAuthError;
|
|
127
|
+
returnTo: string | undefined;
|
|
128
|
+
}
|
|
129
|
+
/** The URL carried no answer from Canopy, and nobody is signed in. */
|
|
130
|
+
| {
|
|
131
|
+
status: "signed-out";
|
|
132
|
+
}
|
|
133
|
+
/** `start()` sent the browser to Canopy to resume a session silently. */
|
|
134
|
+
| {
|
|
135
|
+
status: "redirecting";
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Create a client for a single-page app signing in through Canopy's hosted
|
|
139
|
+
* login as a public OAuth client.
|
|
140
|
+
*
|
|
141
|
+
* Tokens live in this object's memory and nowhere else, so a script injected
|
|
142
|
+
* into the page cannot read them from storage. A reload loses them; `start()`
|
|
143
|
+
* gets them back by sending the browser to Canopy with `prompt=none`, which
|
|
144
|
+
* returns at once while the person's Canopy session lasts.
|
|
145
|
+
*/
|
|
146
|
+
declare function createCanopyBrowserClient(options: CanopyBrowserClientOptions): CanopyBrowserClient;
|
|
147
|
+
declare class CanopyBrowserClient {
|
|
148
|
+
#private;
|
|
149
|
+
constructor(options: CanopyBrowserClientOptions);
|
|
150
|
+
/**
|
|
151
|
+
* Run once when the app loads. Finishes a sign-in when Canopy has just sent
|
|
152
|
+
* the browser back, and otherwise tries to resume Canopy's session without
|
|
153
|
+
* showing a page. Show your sign-in button on `signed-out`, and on `error`
|
|
154
|
+
* when `error.needsSignIn`.
|
|
155
|
+
*/
|
|
156
|
+
start(url?: string): Promise<SignInResult>;
|
|
157
|
+
/** Send the browser to Canopy's sign-in. */
|
|
158
|
+
signIn(signIn?: SignInOptions): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Finish the sign-in Canopy just sent the browser back from: check `state`
|
|
161
|
+
* against this browser's own request, exchange the code with the client id
|
|
162
|
+
* and the PKCE verifier, and take the response parameters out of the URL.
|
|
163
|
+
*/
|
|
164
|
+
handleRedirect(url?: string): Promise<SignInResult>;
|
|
165
|
+
/** Whether tokens are in memory. */
|
|
166
|
+
isSignedIn(): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* A current access token for `Authorization: Bearer`, refreshed first when
|
|
169
|
+
* it is about to expire. `null` when nobody is signed in, or the session has
|
|
170
|
+
* ended (its lifetime ran out or it was revoked).
|
|
171
|
+
*/
|
|
172
|
+
getAccessToken(): Promise<string | null>;
|
|
173
|
+
/**
|
|
174
|
+
* Trade the refresh token for new tokens now. Concurrent calls share one
|
|
175
|
+
* request, so the rotated token is never presented twice from this page.
|
|
176
|
+
* A refused refresh ends the session in memory and calls `onSessionEnd`.
|
|
177
|
+
*/
|
|
178
|
+
refresh(): Promise<void>;
|
|
179
|
+
/** The signed-in person's ID token claims, or `null`. */
|
|
180
|
+
getClaims(): IdTokenClaims | null;
|
|
181
|
+
/**
|
|
182
|
+
* Sign out of the app and of Canopy: revoke the refresh token, forget the
|
|
183
|
+
* tokens, then send the browser to Canopy's end-session endpoint with the
|
|
184
|
+
* ID token as the hint. Skipping the second half would let the next
|
|
185
|
+
* `start()` sign the person straight back in.
|
|
186
|
+
*/
|
|
187
|
+
signOut(): Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { CanopyAuthError, CanopyBrowserClient, type CanopyBrowserClientOptions, CanopyConnectionError, type IdTokenClaims, type PendingStorage, type SignInOptions, type SignInResult, createCanopyBrowserClient, isCanopyAuthError, isCanopyConnectionError };
|