@ghuri/oauth 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +149 -2
- package/dist/chunk-IS4XX7EU.js +68 -0
- package/dist/chunk-IS4XX7EU.js.map +1 -0
- package/dist/chunk-OIBPPYBX.js +112 -0
- package/dist/chunk-OIBPPYBX.js.map +1 -0
- package/dist/chunk-TSKPVGEC.js +717 -0
- package/dist/chunk-TSKPVGEC.js.map +1 -0
- package/dist/index.d.ts +340 -0
- package/dist/index.js +231 -0
- package/dist/index.js.map +1 -0
- package/dist/rest.d.ts +18 -0
- package/dist/rest.js +33 -0
- package/dist/rest.js.map +1 -0
- package/dist/strategy-nxNiKJFS.d.ts +109 -0
- package/dist/testing.d.ts +68 -0
- package/dist/testing.js +191 -0
- package/dist/testing.js.map +1 -0
- package/package.json +51 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts","../src/crypto.ts","../src/jwt.ts","../src/state.ts","../src/oauth2.ts","../src/oidc.ts"],"sourcesContent":["import { GhuriError } from '@ghuri/core'\n\nexport class OAuthError extends GhuriError {\n constructor(\n message: string,\n /** The provider's `error` code, when there is one. */\n readonly code?: string,\n readonly details?: unknown,\n ) {\n super(message)\n }\n}\n\n/** An ID token failed validation (signature, issuer, audience, expiry or nonce). */\nexport class TokenValidationError extends OAuthError {}\n","// Outgoing requests to identity providers: timeouts, JSON/form parsing, and a test hook to stub `fetch`.\nimport { tryGetContext } from '@ghuri/core'\nimport { OAuthError } from './errors'\n\ntype FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>\n\nconst KEY = Symbol.for('ghuri.oauth.fetch')\n\n/** Replace the fetch used for provider requests (tests). Pass undefined to restore `globalThis.fetch`. */\nexport function setProviderFetch(fn: FetchLike | undefined): void {\n ;(globalThis as any)[KEY] = fn\n}\n\nexport function providerFetch(input: string | URL | Request, init: RequestInit = {}): Promise<Response> {\n const fn = ((globalThis as any)[KEY] as FetchLike | undefined) ?? fetch\n const timeout = ((tryGetContext()?.settings.OAUTH as any)?.HTTP_TIMEOUT as number | undefined) ?? 10_000\n const headers = new Headers(init.headers)\n if (!headers.has('user-agent')) headers.set('user-agent', 'ghuri-oauth')\n return fn(input, { ...init, headers, signal: init.signal ?? AbortSignal.timeout(timeout) })\n}\n\n/** Parse a provider response as JSON or `application/x-www-form-urlencoded` (GitHub's legacy token format). */\nexport async function readBody(res: Response): Promise<Record<string, unknown>> {\n const text = await res.text()\n const type = res.headers.get('content-type') ?? ''\n if (\n type.includes('application/x-www-form-urlencoded') ||\n (!type.includes('json') && text.includes('=') && !text.trim().startsWith('{'))\n ) {\n return Object.fromEntries(new URLSearchParams(text))\n }\n try {\n return text ? (JSON.parse(text) as Record<string, unknown>) : {}\n } catch {\n throw new OAuthError(\n `Provider answered ${res.status} with a body that is not JSON.`,\n 'invalid_response',\n text.slice(0, 200),\n )\n }\n}\n\n/** GET a JSON document (userinfo, discovery, JWKS); throws `OAuthError` on HTTP errors. */\nexport async function getJson(url: string, init: RequestInit = {}): Promise<Record<string, any>> {\n const headers = new Headers(init.headers)\n if (!headers.has('accept')) headers.set('accept', 'application/json')\n const res = await providerFetch(url, { ...init, headers })\n const body = await readBody(res)\n if (!res.ok) {\n throw new OAuthError(\n `GET ${new URL(url).origin}${new URL(url).pathname} failed with ${res.status}.`,\n (body.error as string | undefined) ?? 'http_error',\n body,\n )\n }\n return body\n}\n","// An OAuth 2.0 client (RFC 6749 + PKCE, RFC 7636): authorization URLs, code exchange, refresh, revoke.\nimport { OAuthError } from './errors'\nimport { providerFetch, readBody } from './http'\nimport type { OAuthTokens } from './types'\n\nexport interface OAuth2ClientConfig {\n authorizationURL: string\n tokenURL: string\n clientId: string\n clientSecret?: string\n /** How to send client credentials to the token endpoint (default `'body'`). */\n tokenAuth?: 'body' | 'basic'\n revocationURL?: string\n scopeSeparator?: string\n}\n\nexport function toTokens(body: Record<string, unknown>): OAuthTokens {\n const accessToken = body.access_token\n if (typeof accessToken !== 'string' || !accessToken) {\n throw new OAuthError(\n String(body.error_description ?? body.error ?? 'The provider did not return an access token.'),\n typeof body.error === 'string' ? body.error : 'invalid_token_response',\n body,\n )\n }\n const expiresIn = body.expires_in === undefined ? undefined : Number(body.expires_in)\n return {\n accessToken,\n tokenType: typeof body.token_type === 'string' ? body.token_type : undefined,\n refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,\n expiresIn: Number.isFinite(expiresIn) ? expiresIn : undefined,\n expiresAt: Number.isFinite(expiresIn) ? Date.now() + (expiresIn as number) * 1000 : undefined,\n idToken: typeof body.id_token === 'string' ? body.id_token : undefined,\n scope: typeof body.scope === 'string' ? body.scope : undefined,\n raw: body,\n }\n}\n\nexport class OAuth2Client {\n constructor(readonly config: OAuth2ClientConfig) {}\n\n authorizationUrl(opts: {\n redirectUri: string\n state: string\n scope?: string[]\n codeChallenge?: string\n nonce?: string\n params?: Record<string, string>\n }): string {\n const url = new URL(this.config.authorizationURL)\n const q = url.searchParams\n q.set('response_type', 'code')\n q.set('client_id', this.config.clientId)\n q.set('redirect_uri', opts.redirectUri)\n q.set('state', opts.state)\n if (opts.scope?.length) q.set('scope', opts.scope.join(this.config.scopeSeparator ?? ' '))\n if (opts.codeChallenge) {\n q.set('code_challenge', opts.codeChallenge)\n q.set('code_challenge_method', 'S256')\n }\n if (opts.nonce) q.set('nonce', opts.nonce)\n for (const [k, v] of Object.entries(opts.params ?? {})) q.set(k, v)\n return url.toString()\n }\n\n private async tokenRequest(form: Record<string, string>): Promise<OAuthTokens> {\n const body = new URLSearchParams(form)\n const headers: Record<string, string> = {\n 'content-type': 'application/x-www-form-urlencoded',\n accept: 'application/json',\n }\n if (this.config.tokenAuth === 'basic' && this.config.clientSecret !== undefined) {\n const id = encodeURIComponent(this.config.clientId)\n const secret = encodeURIComponent(this.config.clientSecret)\n headers.authorization = `Basic ${btoa(`${id}:${secret}`)}`\n } else {\n body.set('client_id', this.config.clientId)\n if (this.config.clientSecret !== undefined) body.set('client_secret', this.config.clientSecret)\n }\n const res = await providerFetch(this.config.tokenURL, { method: 'POST', headers, body })\n const data = await readBody(res)\n if (!res.ok && !data.error) {\n throw new OAuthError(`The token endpoint answered ${res.status}.`, 'http_error', data)\n }\n return toTokens(data)\n }\n\n exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<OAuthTokens> {\n const form: Record<string, string> = { grant_type: 'authorization_code', code, redirect_uri: redirectUri }\n if (codeVerifier) form.code_verifier = codeVerifier\n return this.tokenRequest(form)\n }\n\n refresh(refreshToken: string, scope?: string[]): Promise<OAuthTokens> {\n const form: Record<string, string> = { grant_type: 'refresh_token', refresh_token: refreshToken }\n if (scope?.length) form.scope = scope.join(this.config.scopeSeparator ?? ' ')\n return this.tokenRequest(form)\n }\n\n /** RFC 7009 token revocation (needs `revocationURL`). */\n async revoke(token: string, hint?: 'access_token' | 'refresh_token'): Promise<void> {\n if (!this.config.revocationURL) throw new OAuthError('This provider has no revocation endpoint.')\n const body = new URLSearchParams({ token, client_id: this.config.clientId })\n if (hint) body.set('token_type_hint', hint)\n if (this.config.clientSecret !== undefined) body.set('client_secret', this.config.clientSecret)\n const res = await providerFetch(this.config.revocationURL, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body,\n })\n if (!res.ok) throw new OAuthError(`Token revocation failed with ${res.status}.`, 'revocation_failed')\n }\n}\n","// WebCrypto helpers (Workers and Node): random values, PKCE, base64url, AES-GCM with a key from SECRET_KEY.\nimport { ImproperlyConfigured, tryGetContext } from '@ghuri/core'\n\nconst enc = new TextEncoder()\nconst dec = new TextDecoder()\n\nexport function b64url(bytes: Uint8Array | ArrayBuffer): string {\n const u = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)\n let s = ''\n for (const b of u) s += String.fromCharCode(b)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nexport function fromB64url(value: string): Uint8Array<ArrayBuffer> {\n const s = value.replace(/-/g, '+').replace(/_/g, '/')\n const bin = atob(s + '='.repeat((4 - (s.length % 4)) % 4))\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out\n}\n\nexport function utf8(value: string): Uint8Array<ArrayBuffer> {\n return enc.encode(value) as Uint8Array<ArrayBuffer>\n}\n\nexport function fromUtf8(bytes: Uint8Array): string {\n return dec.decode(bytes)\n}\n\n/** URL-safe random string with `bytes` bytes of entropy. */\nexport function randomToken(bytes = 32): string {\n return b64url(crypto.getRandomValues(new Uint8Array(bytes)))\n}\n\nexport async function sha256(value: string): Promise<Uint8Array> {\n return new Uint8Array(await crypto.subtle.digest('SHA-256', utf8(value)))\n}\n\n/** PKCE: a verifier and its S256 challenge. */\nexport async function pkcePair(): Promise<{ verifier: string; challenge: string }> {\n const verifier = randomToken(32)\n return { verifier, challenge: b64url(await sha256(verifier)) }\n}\n\nexport function secretKey(): string {\n const key = tryGetContext()?.settings.SECRET_KEY as string | undefined\n if (!key) throw new ImproperlyConfigured('The SECRET_KEY setting must not be empty.')\n return key\n}\n\nfunction fallbackKeys(): string[] {\n return (tryGetContext()?.settings.SECRET_KEY_FALLBACKS as string[] | undefined) ?? []\n}\n\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\nasync function aesKey(secret: string, purpose: string): Promise<CryptoKey> {\n const id = `${purpose}\\u0000${secret}`\n let k = keyCache.get(id)\n if (!k) {\n k = (async () => {\n const base = await crypto.subtle.importKey('raw', utf8(secret), 'HKDF', false, ['deriveKey'])\n return crypto.subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: utf8('ghuri.oauth'), info: utf8(purpose) },\n base,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n })()\n if (keyCache.size > 32) keyCache.clear()\n keyCache.set(id, k)\n }\n return k\n}\n\n/** Encrypt and authenticate a string with a key derived from `SECRET_KEY` for `purpose`. */\nexport async function seal(plaintext: string, purpose: string): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const key = await aesKey(secretKey(), purpose)\n const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, utf8(plaintext)))\n const out = new Uint8Array(iv.length + ct.length)\n out.set(iv)\n out.set(ct, iv.length)\n return b64url(out)\n}\n\n/** Decrypt what `seal()` produced (also with `SECRET_KEY_FALLBACKS`). Null when it fails. */\nexport async function unseal(sealed: string, purpose: string): Promise<string | null> {\n let data: Uint8Array<ArrayBuffer>\n try {\n data = fromB64url(sealed)\n } catch {\n return null\n }\n if (data.length < 13) return null\n const iv = data.slice(0, 12)\n const ct = data.slice(12)\n for (const secret of [secretKey(), ...fallbackKeys()]) {\n try {\n const key = await aesKey(secret, purpose)\n return fromUtf8(new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct)))\n } catch {\n // try the next key\n }\n }\n return null\n}\n\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let r = 0\n for (let i = 0; i < a.length; i++) r |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return r === 0\n}\n","// Minimal JWS verification and signing on WebCrypto (Workers and Node): ID tokens (JWKS), and JWTs we sign\n// ourselves (Sign in with Apple client secrets, test providers).\nimport { b64url, fromB64url, fromUtf8, utf8 } from './crypto'\nimport { TokenValidationError } from './errors'\nimport { getJson } from './http'\n\nexport type JwsAlgorithm =\n | 'RS256'\n | 'RS384'\n | 'RS512'\n | 'PS256'\n | 'PS384'\n | 'PS512'\n | 'ES256'\n | 'ES384'\n | 'ES512'\n | 'EdDSA'\n\nexport const SUPPORTED_ALGORITHMS: readonly JwsAlgorithm[] = [\n 'RS256',\n 'RS384',\n 'RS512',\n 'PS256',\n 'PS384',\n 'PS512',\n 'ES256',\n 'ES384',\n 'ES512',\n 'EdDSA',\n]\n\ntype Jwk = JsonWebKey & { kid?: string; alg?: string; use?: string; crv?: string }\n\nfunction params(alg: JwsAlgorithm): { importParams: any; verifyParams: any } {\n const hash = `SHA-${alg.slice(2)}`\n switch (alg.slice(0, 2)) {\n case 'RS':\n return {\n importParams: { name: 'RSASSA-PKCS1-v1_5', hash },\n verifyParams: { name: 'RSASSA-PKCS1-v1_5' },\n }\n case 'PS':\n return {\n importParams: { name: 'RSA-PSS', hash },\n verifyParams: { name: 'RSA-PSS', saltLength: Number(alg.slice(2)) / 8 },\n }\n case 'ES': {\n const namedCurve = { ES256: 'P-256', ES384: 'P-384', ES512: 'P-521' }[alg as 'ES256']\n return { importParams: { name: 'ECDSA', namedCurve }, verifyParams: { name: 'ECDSA', hash } }\n }\n default:\n return { importParams: { name: 'Ed25519' }, verifyParams: { name: 'Ed25519' } }\n }\n}\n\nexport interface DecodedJwt {\n header: { alg: string; kid?: string; typ?: string; [k: string]: unknown }\n payload: Record<string, any>\n signingInput: string\n signature: Uint8Array<ArrayBuffer>\n}\n\n/** Split and decode without verifying. */\nexport function decodeJwt(token: string): DecodedJwt {\n const parts = token.split('.')\n if (parts.length !== 3) throw new TokenValidationError('Malformed JWT.')\n try {\n return {\n header: JSON.parse(fromUtf8(fromB64url(parts[0]))),\n payload: JSON.parse(fromUtf8(fromB64url(parts[1]))),\n signingInput: `${parts[0]}.${parts[1]}`,\n signature: fromB64url(parts[2]),\n }\n } catch {\n throw new TokenValidationError('Malformed JWT.')\n }\n}\n\n// ---- JWKS ----\n\nconst JWKS_TTL = 60 * 60 * 1000\nconst JWKS_MIN_REFRESH = 60 * 1000\nconst jwksCache = new Map<string, { keys: Jwk[]; fetchedAt: number }>()\n\nasync function fetchJwks(uri: string): Promise<Jwk[]> {\n const doc = await getJson(uri)\n const keys = Array.isArray(doc.keys) ? (doc.keys as Jwk[]) : []\n jwksCache.set(uri, { keys, fetchedAt: Date.now() })\n return keys\n}\n\nasync function findKey(uri: string, kid: string | undefined, kty: string): Promise<Jwk | undefined> {\n const pick = (keys: Jwk[]) =>\n keys.find((k) => (kid === undefined || k.kid === kid) && k.kty === kty && (!k.use || k.use === 'sig'))\n const cached = jwksCache.get(uri)\n if (cached && Date.now() - cached.fetchedAt < JWKS_TTL) {\n const k = pick(cached.keys)\n if (k) return k\n // A rotated key: refetch, but not more than once a minute.\n if (Date.now() - cached.fetchedAt < JWKS_MIN_REFRESH) return undefined\n }\n return pick(await fetchJwks(uri))\n}\n\n/** Forget cached JWKS (tests, key rotation). */\nexport function clearJwksCache(): void {\n jwksCache.clear()\n}\n\n// ---- verify ----\n\nexport interface VerifyOptions {\n /** Where to fetch the signing keys. */\n jwksUri?: string\n /** Or the keys themselves. */\n keys?: Jwk[]\n /** Expected `iss`, or a predicate (multi-tenant issuers). */\n issuer?: string | ((iss: string) => boolean)\n /** Expected `aud` (the client id). */\n audience?: string\n nonce?: string\n /** Clock skew allowed, seconds (default 60). */\n leeway?: number\n algorithms?: readonly JwsAlgorithm[]\n}\n\nfunction ktyFor(alg: JwsAlgorithm): string {\n return alg.startsWith('ES') ? 'EC' : alg === 'EdDSA' ? 'OKP' : 'RSA'\n}\n\n/** Verify a JWS and its standard claims; returns the payload. Throws `TokenValidationError`. */\nexport async function verifyJwt(token: string, opts: VerifyOptions): Promise<Record<string, any>> {\n const { header, payload, signingInput, signature } = decodeJwt(token)\n const alg = header.alg as JwsAlgorithm\n if (!(opts.algorithms ?? SUPPORTED_ALGORITHMS).includes(alg)) {\n throw new TokenValidationError(`Unsupported or disallowed JWT algorithm '${header.alg}'.`)\n }\n const kty = ktyFor(alg)\n let jwk: Jwk | undefined\n if (opts.keys)\n jwk = opts.keys.find((k) => (header.kid === undefined || k.kid === header.kid) && k.kty === kty)\n else if (opts.jwksUri) jwk = await findKey(opts.jwksUri, header.kid, kty)\n if (!jwk) throw new TokenValidationError(`No signing key found for kid '${header.kid ?? ''}'.`)\n const { importParams, verifyParams } = params(alg)\n let ok: boolean\n try {\n const { alg: _a, use: _u, key_ops: _k, ...bare } = jwk as any\n const key = await crypto.subtle.importKey('jwk', bare, importParams, false, ['verify'])\n ok = await crypto.subtle.verify(verifyParams, key, signature, utf8(signingInput))\n } catch {\n ok = false\n }\n if (!ok) throw new TokenValidationError('Invalid JWT signature.')\n\n const now = Math.floor(Date.now() / 1000)\n const leeway = opts.leeway ?? 60\n if (opts.issuer !== undefined) {\n const iss = String(payload.iss ?? '')\n const good = typeof opts.issuer === 'function' ? opts.issuer(iss) : iss === opts.issuer\n if (!good) throw new TokenValidationError(`Unexpected issuer '${iss}'.`)\n }\n if (opts.audience !== undefined) {\n const aud = Array.isArray(payload.aud) ? payload.aud : [payload.aud]\n if (!aud.includes(opts.audience))\n throw new TokenValidationError('The token was issued for another client.')\n if (aud.length > 1 && payload.azp !== undefined && payload.azp !== opts.audience) {\n throw new TokenValidationError('The token was issued for another client (azp).')\n }\n }\n if (typeof payload.exp !== 'number' || payload.exp + leeway < now)\n throw new TokenValidationError('The token expired.')\n if (typeof payload.nbf === 'number' && payload.nbf - leeway > now)\n throw new TokenValidationError('The token is not valid yet.')\n if (typeof payload.iat === 'number' && payload.iat - leeway > now)\n throw new TokenValidationError('The token was issued in the future.')\n if (opts.nonce !== undefined && payload.nonce !== opts.nonce)\n throw new TokenValidationError('Nonce mismatch.')\n return payload\n}\n\n// ---- sign ----\n\n/** Sign a JWT (ES256/RS256/EdDSA…). The key is a private JWK or a CryptoKey. */\nexport async function signJwt(\n payload: Record<string, unknown>,\n key: Jwk | CryptoKey,\n alg: JwsAlgorithm,\n header: Record<string, unknown> = {},\n): Promise<string> {\n const { importParams, verifyParams } = params(alg)\n const cryptoKey =\n key instanceof CryptoKey\n ? key\n : await crypto.subtle.importKey(\n 'jwk',\n (({ alg: _a, use: _u, key_ops: _k, ...bare }: any) => bare)(key),\n importParams,\n false,\n ['sign'],\n )\n const h = b64url(utf8(JSON.stringify({ alg, typ: 'JWT', ...header })))\n const p = b64url(utf8(JSON.stringify(payload)))\n const sig = await crypto.subtle.sign(verifyParams, cryptoKey, utf8(`${h}.${p}`))\n return `${h}.${p}.${b64url(sig)}`\n}\n\n/** Import a PKCS#8 PEM private key (e.g. Apple's `.p8`) for `signJwt`. */\nexport async function importPkcs8(pem: string, alg: JwsAlgorithm): Promise<CryptoKey> {\n const body = pem.replace(/-----(BEGIN|END) PRIVATE KEY-----/g, '').replace(/\\s+/g, '')\n const der = Uint8Array.from(atob(body), (ch) => ch.charCodeAt(0))\n return crypto.subtle.importKey('pkcs8', der, params(alg).importParams, false, ['sign'])\n}\n","// Round-trip state for redirect flows (state, PKCE verifier, nonce, next) in an encrypted, short-lived cookie,\n// so OAuth works without sessions. Several pending flows can coexist (two tabs).\nimport { isSecure, tryGetContext } from '@ghuri/core'\nimport type { Context } from 'hono'\nimport { deleteCookie, getCookie, setCookie } from 'hono/cookie'\nimport { seal, unseal } from './crypto'\n\nexport interface PendingFlow {\n state: string\n strategy: string\n redirectUri: string\n verifier?: string\n nonce?: string\n next?: string\n extra?: Record<string, string>\n /** ms since epoch */\n createdAt: number\n}\n\nconst PURPOSE = 'oauth-state'\nconst MAX_FLOWS = 5\n\nexport function stateSettings(): { cookie: string; maxAge: number } {\n const s = (tryGetContext()?.settings.OAUTH ?? {}) as { STATE_COOKIE?: string; STATE_MAX_AGE?: number }\n return { cookie: s.STATE_COOKIE ?? 'ghuri_oauth', maxAge: s.STATE_MAX_AGE ?? 600 }\n}\n\nasync function read(c: Context): Promise<PendingFlow[]> {\n const { cookie, maxAge } = stateSettings()\n const raw = getCookie(c, cookie)\n if (!raw) return []\n const json = await unseal(raw, PURPOSE)\n if (!json) return []\n try {\n const flows = JSON.parse(json) as PendingFlow[]\n const now = Date.now()\n return Array.isArray(flows) ? flows.filter((f) => f && now - f.createdAt <= maxAge * 1000) : []\n } catch {\n return []\n }\n}\n\nasync function write(c: Context, flows: PendingFlow[]): Promise<void> {\n const { cookie, maxAge } = stateSettings()\n if (!flows.length) {\n deleteCookie(c, cookie, { path: '/', secure: isSecure(c) })\n return\n }\n setCookie(c, cookie, await seal(JSON.stringify(flows), PURPOSE), {\n path: '/',\n httpOnly: true,\n secure: isSecure(c),\n // Lax: the provider's redirect back is a top-level GET navigation, which Lax cookies accompany.\n sameSite: 'Lax',\n maxAge,\n })\n}\n\n/** Remember a flow until the callback (newest five kept). */\nexport async function savePending(c: Context, flow: PendingFlow): Promise<void> {\n const flows = (await read(c)).filter((f) => f.state !== flow.state)\n flows.push(flow)\n await write(c, flows.slice(-MAX_FLOWS))\n}\n\n/** Take the flow for `state` (single use). Null when it is unknown or expired. */\nexport async function takePending(c: Context, state: string | undefined): Promise<PendingFlow | null> {\n if (!state) return null\n const flows = await read(c)\n const flow = flows.find((f) => f.state === state) ?? null\n if (flow)\n await write(\n c,\n flows.filter((f) => f !== flow),\n )\n return flow\n}\n","// The authorization code flow as a strategy: start (redirect to the provider), callback (exchange, profile, verify).\nimport { getHost, isSecure, tryGetContext } from '@ghuri/core'\nimport type { Context } from 'hono'\nimport { OAuth2Client } from './client'\nimport { pkcePair, randomToken } from './crypto'\nimport { OAuthError } from './errors'\nimport { getJson } from './http'\nimport { type PendingFlow, savePending, takePending } from './state'\nimport { Strategy } from './strategy'\nimport type {\n AuthenticateOptions,\n EnvValue,\n OAuthTokens,\n Profile,\n StrategyResult,\n StrategyUser,\n} from './types'\n\nexport type MaybePromise<T> = T | Promise<T>\n\n/** Resolve a setting that may be a function of env (and async, for computed secrets). */\nexport async function envValue<T>(\n value: EnvValue<T> | ((env: Record<string, any>) => Promise<T>),\n): Promise<T> {\n if (typeof value === 'function') {\n return (value as (env: Record<string, any>) => MaybePromise<T>)(tryGetContext()?.env ?? {})\n }\n return value\n}\n\nexport interface VerifyParams {\n profile: Profile\n tokens: OAuthTokens\n c: Context\n /** The flow this callback belongs to: `next`, `extra` (per-request state). */\n flow: PendingFlow\n}\n\nexport type VerifyResult =\n | StrategyUser\n | null\n | false\n | { user: StrategyUser; info?: Record<string, unknown> }\nexport type VerifyFunction = (params: VerifyParams) => Promise<VerifyResult>\n\nexport interface ProfileContext {\n c: Context\n getJson(url: string, init?: RequestInit): Promise<Record<string, any>>\n /** GET with `Authorization: Bearer <access token>`. */\n getJsonAuthed(url: string, init?: RequestInit): Promise<Record<string, any>>\n}\n\nexport interface OAuth2StrategyOptions {\n /** Strategy and profile provider name (default `'oauth2'`). */\n name?: string\n authorizationURL: EnvValue<string>\n tokenURL: EnvValue<string>\n clientId: EnvValue<string>\n clientSecret?: EnvValue<string | undefined> | ((env: Record<string, any>) => Promise<string>)\n /** Path or absolute URL of the callback (default: the path of the current request). */\n callbackURL?: EnvValue<string>\n scope?: string[]\n scopeSeparator?: string\n /** PKCE with S256 (default true). */\n pkce?: boolean\n authorizationParams?: Record<string, string> | ((c: Context) => Record<string, string>)\n tokenAuth?: 'body' | 'basic'\n revocationURL?: EnvValue<string | undefined>\n /** Fetch the profile from this URL with the access token, unless `profile` is given. */\n userInfoURL?: EnvValue<string>\n /** Build the normalized profile from the tokens. */\n profile?: (tokens: OAuthTokens, ctx: ProfileContext) => Promise<Profile>\n}\n\n/** Map a typical userinfo document to a `Profile`. */\nexport function profileFromClaims(provider: string, raw: Record<string, any>): Profile {\n const id = raw.sub ?? raw.id ?? raw.user_id\n if (id === undefined || id === null)\n throw new OAuthError('The provider profile has no user id.', 'invalid_profile')\n const verified = raw.email_verified ?? raw.verified_email\n return {\n provider,\n id: String(id),\n email: typeof raw.email === 'string' ? raw.email : undefined,\n emailVerified: verified === undefined ? undefined : verified === true || verified === 'true',\n username: raw.preferred_username ?? raw.login ?? raw.username ?? raw.nickname ?? undefined,\n name: raw.name ?? undefined,\n firstName: raw.given_name ?? raw.first_name ?? undefined,\n lastName: raw.family_name ?? raw.last_name ?? undefined,\n avatarUrl: raw.picture ?? raw.avatar_url ?? undefined,\n raw,\n }\n}\n\nfunction origin(c: Context): string {\n const base = (tryGetContext()?.settings.OAUTH as any)?.CALLBACK_BASE_URL as string | undefined\n if (base) return base.replace(/\\/+$/, '')\n return `${isSecure(c) ? 'https' : 'http'}://${getHost(c)}`\n}\n\n/**\n * OAuth 2.0 authorization code flow (with PKCE). Mount one view for both legs:\n * `path('login/acme/', authenticate('acme'))` — without `code` it redirects to the provider, with it it logs in.\n */\nexport class OAuth2Strategy extends Strategy {\n readonly name: string\n\n constructor(\n readonly options: OAuth2StrategyOptions,\n readonly verify: VerifyFunction,\n ) {\n super()\n this.name = options.name ?? 'oauth2'\n }\n\n async client(): Promise<OAuth2Client> {\n const o = this.options\n return new OAuth2Client({\n authorizationURL: await envValue(o.authorizationURL),\n tokenURL: await envValue(o.tokenURL),\n clientId: await envValue(o.clientId),\n clientSecret: o.clientSecret === undefined ? undefined : await envValue(o.clientSecret),\n tokenAuth: o.tokenAuth,\n revocationURL: o.revocationURL === undefined ? undefined : await envValue(o.revocationURL),\n scopeSeparator: o.scopeSeparator,\n })\n }\n\n async callbackUrl(c: Context): Promise<string> {\n const configured =\n this.options.callbackURL === undefined ? undefined : await envValue(this.options.callbackURL)\n if (configured && /^https?:\\/\\//i.test(configured)) return configured\n return `${origin(c)}${configured ?? c.req.path}`\n }\n\n /** Extra authorization parameters and flow fields; OIDC adds a nonce. */\n protected async startExtras(_c: Context): Promise<{ params?: Record<string, string>; nonce?: string }> {\n return {}\n }\n\n protected scopes(options: AuthenticateOptions): string[] {\n return [...new Set([...(this.options.scope ?? []), ...(options.scope ?? [])])]\n }\n\n async authenticate(c: Context, options: AuthenticateOptions): Promise<StrategyResult> {\n const q = c.req.query()\n if (q.error) {\n await takePending(c, q.state)\n const message =\n q.error === 'access_denied' ? 'You cancelled the login.' : q.error_description || q.error\n return { type: 'fail', status: 401, message }\n }\n if (q.code) return this.callback(c, q.code, q.state)\n return this.start(c, options)\n }\n\n protected async start(c: Context, options: AuthenticateOptions): Promise<StrategyResult> {\n const client = await this.client()\n const state = randomToken(24)\n const pkce = this.options.pkce === false ? undefined : await pkcePair()\n const extras = await this.startExtras(c)\n const redirectUri = await this.callbackUrl(c)\n const next = options.next ?? c.req.query('next')\n await savePending(c, {\n state,\n strategy: this.name,\n redirectUri,\n verifier: pkce?.verifier,\n nonce: extras.nonce,\n next: next || undefined,\n extra: options.state,\n createdAt: Date.now(),\n })\n const ap = this.options.authorizationParams\n const params = { ...(typeof ap === 'function' ? ap(c) : (ap ?? {})), ...(extras.params ?? {}) }\n const url = client.authorizationUrl({\n redirectUri,\n state,\n scope: this.scopes(options),\n codeChallenge: pkce?.challenge,\n nonce: extras.nonce,\n params,\n })\n return { type: 'redirect', url, status: 302 }\n }\n\n protected async callback(c: Context, code: string, state: string | undefined): Promise<StrategyResult> {\n const flow = await takePending(c, state)\n if (!flow || flow.strategy !== this.name) {\n return {\n type: 'fail',\n status: 400,\n message: 'This login link expired or was already used. Please try again.',\n }\n }\n let tokens: OAuthTokens\n let profile: Profile\n try {\n const client = await this.client()\n tokens = await client.exchangeCode(code, flow.redirectUri, flow.verifier)\n profile = await this.userProfile(tokens, c, flow)\n } catch (err) {\n if (err instanceof OAuthError) return { type: 'fail', status: 401, message: err.message }\n throw err\n }\n const result = await this.verify({ profile, tokens, c, flow })\n if (!result) return { type: 'fail', status: 401, message: 'This account is not allowed to log in.' }\n const info = { profile, tokens, next: flow.next, state: flow.extra }\n if (\n typeof result === 'object' &&\n 'user' in result &&\n Object.keys(result).every((k) => k === 'user' || k === 'info')\n ) {\n return { type: 'success', user: result.user, info: { ...info, ...(result.info ?? {}) } }\n }\n return { type: 'success', user: result, info }\n }\n\n protected profileContext(c: Context, tokens: OAuthTokens): ProfileContext {\n return {\n c,\n getJson,\n getJsonAuthed: (url, init = {}) => {\n const headers = new Headers(init.headers)\n headers.set('authorization', `Bearer ${tokens.accessToken}`)\n return getJson(url, { ...init, headers })\n },\n }\n }\n\n /** The normalized profile for these tokens. */\n async userProfile(tokens: OAuthTokens, c: Context, _flow?: PendingFlow): Promise<Profile> {\n const ctx = this.profileContext(c, tokens)\n if (this.options.profile) return this.options.profile(tokens, ctx)\n if (!this.options.userInfoURL) {\n throw new OAuthError(`Strategy '${this.name}' needs a userInfoURL or a profile() function.`)\n }\n return profileFromClaims(this.name, await ctx.getJsonAuthed(await envValue(this.options.userInfoURL)))\n }\n}\n","// OpenID Connect: discovery, nonce, ID token validation against the issuer's JWKS, userinfo.\nimport type { Context } from 'hono'\nimport { randomToken } from './crypto'\nimport { OAuthError, TokenValidationError } from './errors'\nimport { getJson } from './http'\nimport { type JwsAlgorithm, verifyJwt } from './jwt'\nimport {\n envValue,\n OAuth2Strategy,\n type OAuth2StrategyOptions,\n profileFromClaims,\n type VerifyFunction,\n} from './oauth2'\nimport type { PendingFlow } from './state'\nimport type { EnvValue, OAuthTokens, Profile } from './types'\n\nexport interface DiscoveryDocument {\n issuer: string\n authorization_endpoint: string\n token_endpoint: string\n userinfo_endpoint?: string\n jwks_uri: string\n revocation_endpoint?: string\n end_session_endpoint?: string\n id_token_signing_alg_values_supported?: string[]\n [k: string]: unknown\n}\n\nconst DISCOVERY_TTL = 60 * 60 * 1000\nconst discoveryCache = new Map<string, { doc: DiscoveryDocument; fetchedAt: number }>()\n\n/** Fetch (and cache for an hour) `<issuer>/.well-known/openid-configuration`. */\nexport async function discover(issuer: string): Promise<DiscoveryDocument> {\n const key = issuer.replace(/\\/+$/, '')\n const hit = discoveryCache.get(key)\n if (hit && Date.now() - hit.fetchedAt < DISCOVERY_TTL) return hit.doc\n const doc = (await getJson(`${key}/.well-known/openid-configuration`)) as DiscoveryDocument\n for (const f of ['authorization_endpoint', 'token_endpoint', 'jwks_uri'] as const) {\n if (typeof doc[f] !== 'string') throw new OAuthError(`The discovery document of ${key} has no ${f}.`)\n }\n discoveryCache.set(key, { doc, fetchedAt: Date.now() })\n return doc\n}\n\nexport function clearDiscoveryCache(): void {\n discoveryCache.clear()\n}\n\nexport interface OIDCStrategyOptions\n extends Omit<OAuth2StrategyOptions, 'authorizationURL' | 'tokenURL' | 'userInfoURL' | 'name'> {\n name?: string\n issuer: EnvValue<string>\n /** Skip discovery by giving the endpoints (any you leave out still come from discovery). */\n endpoints?: Partial<\n Pick<\n DiscoveryDocument,\n 'authorization_endpoint' | 'token_endpoint' | 'userinfo_endpoint' | 'jwks_uri' | 'revocation_endpoint'\n >\n >\n /**\n * How to check the ID token's `iss`: exact match with the issuer (default), or a predicate for multi-tenant\n * issuers (Microsoft's `common` endpoint).\n */\n issuerValidation?: (iss: string) => boolean\n /** Also call the userinfo endpoint and merge its claims (default true when the issuer has one). */\n fetchUserInfo?: boolean\n algorithms?: readonly JwsAlgorithm[]\n}\n\n/** OpenID Connect login: OAuth2 + discovery + validated ID token. Default scope `openid email profile`. */\nexport class OIDCStrategy extends OAuth2Strategy {\n declare readonly options: OAuth2StrategyOptions & OIDCStrategyOptions\n\n constructor(options: OIDCStrategyOptions, verify: VerifyFunction) {\n const lazy = (pick: (d: DiscoveryDocument) => string | undefined, override?: string) => async () =>\n override ?? pick(await discover(await envValue(options.issuer))) ?? ''\n super(\n {\n ...options,\n name: options.name ?? 'oidc',\n scope: options.scope ?? ['openid', 'email', 'profile'],\n authorizationURL: lazy(\n (d) => d.authorization_endpoint,\n options.endpoints?.authorization_endpoint,\n ) as any,\n tokenURL: lazy((d) => d.token_endpoint, options.endpoints?.token_endpoint) as any,\n revocationURL: (options.revocationURL ??\n lazy((d) => d.revocation_endpoint, options.endpoints?.revocation_endpoint)) as any,\n },\n verify,\n )\n }\n\n async discovery(): Promise<DiscoveryDocument> {\n const e = this.options.endpoints\n const issuer = await envValue(this.options.issuer)\n if (e?.authorization_endpoint && e.token_endpoint && e.jwks_uri) {\n return { issuer, ...e } as DiscoveryDocument\n }\n return { ...(await discover(issuer)), ...(e ?? {}) }\n }\n\n protected override async startExtras(_c: Context) {\n return { nonce: randomToken(16) }\n }\n\n /** Validate an ID token from this provider (also used for tokens obtained by native SDKs). */\n async validateIdToken(idToken: string, nonce?: string): Promise<Record<string, any>> {\n const doc = await this.discovery()\n const issuer = await envValue(this.options.issuer)\n return verifyJwt(idToken, {\n jwksUri: doc.jwks_uri,\n issuer: this.options.issuerValidation ?? doc.issuer ?? issuer,\n audience: await envValue(this.options.clientId),\n nonce,\n algorithms: this.options.algorithms,\n })\n }\n\n override async userProfile(tokens: OAuthTokens, c: Context, flow?: PendingFlow): Promise<Profile> {\n if (!tokens.idToken) throw new OAuthError('The provider did not return an ID token.', 'missing_id_token')\n let claims: Record<string, any>\n try {\n claims = await this.validateIdToken(tokens.idToken, flow?.nonce)\n } catch (err) {\n if (err instanceof TokenValidationError)\n throw new OAuthError(`Invalid ID token: ${err.message}`, 'invalid_id_token')\n throw err\n }\n const doc = await this.discovery()\n if (this.options.fetchUserInfo !== false && doc.userinfo_endpoint) {\n const info = await this.profileContext(c, tokens).getJsonAuthed(doc.userinfo_endpoint)\n if (info.sub !== undefined && String(info.sub) !== String(claims.sub)) {\n throw new OAuthError('The userinfo subject does not match the ID token.', 'invalid_userinfo')\n }\n claims = { ...info, ...claims }\n }\n if (this.options.profile)\n return this.options.profile(\n { ...tokens, raw: { ...tokens.raw, claims } },\n this.profileContext(c, tokens),\n )\n return profileFromClaims(this.name, claims)\n }\n}\n"],"mappings":"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","names":["tryGetContext","tryGetContext","tryGetContext","isSecure","tryGetContext","tryGetContext","isSecure","params"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { ViewFunction, GhuriError } from '@ghuri/core';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
import { S as StrategyResult, a as StrategyRef, A as AuthenticateOptions, b as StrategyUser, O as OAuthTokens, c as Strategy, E as EnvValue, P as Profile } from './strategy-nxNiKJFS.js';
|
|
4
|
+
export { C as CustomStrategy, r as resolveStrategy, s as strategies } from './strategy-nxNiKJFS.js';
|
|
5
|
+
|
|
6
|
+
type NamedResult = StrategyResult & {
|
|
7
|
+
strategy?: string;
|
|
8
|
+
};
|
|
9
|
+
/** Run strategies in order until one does not `pass`. All pass → fail 401. Thrown errors → `error`. */
|
|
10
|
+
declare function runStrategy(c: Context, refs: StrategyRef | StrategyRef[], options?: AuthenticateOptions): Promise<NamedResult>;
|
|
11
|
+
/** The name `login()` stores in the session: the first configured backend that can load users. */
|
|
12
|
+
declare function loginBackendName(): string;
|
|
13
|
+
declare function userInfo(user: any): Record<string, unknown>;
|
|
14
|
+
/** Log `user` in and answer as `options` say. Exposed for packages that run strategies themselves. */
|
|
15
|
+
declare function completeLogin(c: Context, user: StrategyUser, info: Record<string, unknown> | undefined, options: AuthenticateOptions): Promise<Response>;
|
|
16
|
+
declare function failureResponse(c: Context, result: {
|
|
17
|
+
status?: number;
|
|
18
|
+
challenge?: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
}, options: AuthenticateOptions): Response;
|
|
21
|
+
/**
|
|
22
|
+
* A view that runs the strategies: `path('login/github/', authenticate('github', { successRedirect: '/' }))`.
|
|
23
|
+
* Several names are tried in order (the first that does not pass wins).
|
|
24
|
+
*/
|
|
25
|
+
declare function authenticate(refs: StrategyRef | StrategyRef[], options?: AuthenticateOptions): ViewFunction;
|
|
26
|
+
|
|
27
|
+
interface OAuth2ClientConfig {
|
|
28
|
+
authorizationURL: string;
|
|
29
|
+
tokenURL: string;
|
|
30
|
+
clientId: string;
|
|
31
|
+
clientSecret?: string;
|
|
32
|
+
/** How to send client credentials to the token endpoint (default `'body'`). */
|
|
33
|
+
tokenAuth?: 'body' | 'basic';
|
|
34
|
+
revocationURL?: string;
|
|
35
|
+
scopeSeparator?: string;
|
|
36
|
+
}
|
|
37
|
+
declare function toTokens(body: Record<string, unknown>): OAuthTokens;
|
|
38
|
+
declare class OAuth2Client {
|
|
39
|
+
readonly config: OAuth2ClientConfig;
|
|
40
|
+
constructor(config: OAuth2ClientConfig);
|
|
41
|
+
authorizationUrl(opts: {
|
|
42
|
+
redirectUri: string;
|
|
43
|
+
state: string;
|
|
44
|
+
scope?: string[];
|
|
45
|
+
codeChallenge?: string;
|
|
46
|
+
nonce?: string;
|
|
47
|
+
params?: Record<string, string>;
|
|
48
|
+
}): string;
|
|
49
|
+
private tokenRequest;
|
|
50
|
+
exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<OAuthTokens>;
|
|
51
|
+
refresh(refreshToken: string, scope?: string[]): Promise<OAuthTokens>;
|
|
52
|
+
/** RFC 7009 token revocation (needs `revocationURL`). */
|
|
53
|
+
revoke(token: string, hint?: 'access_token' | 'refresh_token'): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare function b64url(bytes: Uint8Array | ArrayBuffer): string;
|
|
57
|
+
declare function fromB64url(value: string): Uint8Array<ArrayBuffer>;
|
|
58
|
+
declare function utf8(value: string): Uint8Array<ArrayBuffer>;
|
|
59
|
+
/** URL-safe random string with `bytes` bytes of entropy. */
|
|
60
|
+
declare function randomToken(bytes?: number): string;
|
|
61
|
+
declare function sha256(value: string): Promise<Uint8Array>;
|
|
62
|
+
/** PKCE: a verifier and its S256 challenge. */
|
|
63
|
+
declare function pkcePair(): Promise<{
|
|
64
|
+
verifier: string;
|
|
65
|
+
challenge: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** Encrypt and authenticate a string with a key derived from `SECRET_KEY` for `purpose`. */
|
|
68
|
+
declare function seal(plaintext: string, purpose: string): Promise<string>;
|
|
69
|
+
/** Decrypt what `seal()` produced (also with `SECRET_KEY_FALLBACKS`). Null when it fails. */
|
|
70
|
+
declare function unseal(sealed: string, purpose: string): Promise<string | null>;
|
|
71
|
+
declare function timingSafeEqual(a: string, b: string): boolean;
|
|
72
|
+
|
|
73
|
+
declare class OAuthError extends GhuriError {
|
|
74
|
+
/** The provider's `error` code, when there is one. */
|
|
75
|
+
readonly code?: string | undefined;
|
|
76
|
+
readonly details?: unknown | undefined;
|
|
77
|
+
constructor(message: string,
|
|
78
|
+
/** The provider's `error` code, when there is one. */
|
|
79
|
+
code?: string | undefined, details?: unknown | undefined);
|
|
80
|
+
}
|
|
81
|
+
/** An ID token failed validation (signature, issuer, audience, expiry or nonce). */
|
|
82
|
+
declare class TokenValidationError extends OAuthError {
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
86
|
+
/** Replace the fetch used for provider requests (tests). Pass undefined to restore `globalThis.fetch`. */
|
|
87
|
+
declare function setProviderFetch(fn: FetchLike | undefined): void;
|
|
88
|
+
declare function providerFetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
89
|
+
/** Parse a provider response as JSON or `application/x-www-form-urlencoded` (GitHub's legacy token format). */
|
|
90
|
+
declare function readBody(res: Response): Promise<Record<string, unknown>>;
|
|
91
|
+
/** GET a JSON document (userinfo, discovery, JWKS); throws `OAuthError` on HTTP errors. */
|
|
92
|
+
declare function getJson(url: string, init?: RequestInit): Promise<Record<string, any>>;
|
|
93
|
+
|
|
94
|
+
type JwsAlgorithm = 'RS256' | 'RS384' | 'RS512' | 'PS256' | 'PS384' | 'PS512' | 'ES256' | 'ES384' | 'ES512' | 'EdDSA';
|
|
95
|
+
declare const SUPPORTED_ALGORITHMS: readonly JwsAlgorithm[];
|
|
96
|
+
type Jwk = JsonWebKey & {
|
|
97
|
+
kid?: string;
|
|
98
|
+
alg?: string;
|
|
99
|
+
use?: string;
|
|
100
|
+
crv?: string;
|
|
101
|
+
};
|
|
102
|
+
interface DecodedJwt {
|
|
103
|
+
header: {
|
|
104
|
+
alg: string;
|
|
105
|
+
kid?: string;
|
|
106
|
+
typ?: string;
|
|
107
|
+
[k: string]: unknown;
|
|
108
|
+
};
|
|
109
|
+
payload: Record<string, any>;
|
|
110
|
+
signingInput: string;
|
|
111
|
+
signature: Uint8Array<ArrayBuffer>;
|
|
112
|
+
}
|
|
113
|
+
/** Split and decode without verifying. */
|
|
114
|
+
declare function decodeJwt(token: string): DecodedJwt;
|
|
115
|
+
/** Forget cached JWKS (tests, key rotation). */
|
|
116
|
+
declare function clearJwksCache(): void;
|
|
117
|
+
interface VerifyOptions {
|
|
118
|
+
/** Where to fetch the signing keys. */
|
|
119
|
+
jwksUri?: string;
|
|
120
|
+
/** Or the keys themselves. */
|
|
121
|
+
keys?: Jwk[];
|
|
122
|
+
/** Expected `iss`, or a predicate (multi-tenant issuers). */
|
|
123
|
+
issuer?: string | ((iss: string) => boolean);
|
|
124
|
+
/** Expected `aud` (the client id). */
|
|
125
|
+
audience?: string;
|
|
126
|
+
nonce?: string;
|
|
127
|
+
/** Clock skew allowed, seconds (default 60). */
|
|
128
|
+
leeway?: number;
|
|
129
|
+
algorithms?: readonly JwsAlgorithm[];
|
|
130
|
+
}
|
|
131
|
+
/** Verify a JWS and its standard claims; returns the payload. Throws `TokenValidationError`. */
|
|
132
|
+
declare function verifyJwt(token: string, opts: VerifyOptions): Promise<Record<string, any>>;
|
|
133
|
+
/** Sign a JWT (ES256/RS256/EdDSA…). The key is a private JWK or a CryptoKey. */
|
|
134
|
+
declare function signJwt(payload: Record<string, unknown>, key: Jwk | CryptoKey, alg: JwsAlgorithm, header?: Record<string, unknown>): Promise<string>;
|
|
135
|
+
/** Import a PKCS#8 PEM private key (e.g. Apple's `.p8`) for `signJwt`. */
|
|
136
|
+
declare function importPkcs8(pem: string, alg: JwsAlgorithm): Promise<CryptoKey>;
|
|
137
|
+
|
|
138
|
+
interface LocalStrategyOptions {
|
|
139
|
+
name?: string;
|
|
140
|
+
usernameField?: string;
|
|
141
|
+
passwordField?: string;
|
|
142
|
+
}
|
|
143
|
+
type LocalVerify = (params: {
|
|
144
|
+
username: string;
|
|
145
|
+
password: string;
|
|
146
|
+
c: Context;
|
|
147
|
+
}) => Promise<StrategyUser | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Username and password from a JSON or form POST. The default `verify` is `@ghuri/auth`'s `authenticate()`, so
|
|
150
|
+
* `AUTHENTICATION_BACKENDS`, password hashers and `userLoginFailed` all apply.
|
|
151
|
+
*/
|
|
152
|
+
declare class LocalStrategy extends Strategy {
|
|
153
|
+
readonly verify: LocalVerify;
|
|
154
|
+
readonly name: string;
|
|
155
|
+
private readonly usernameField;
|
|
156
|
+
private readonly passwordField;
|
|
157
|
+
constructor(options?: LocalStrategyOptions, verify?: LocalVerify);
|
|
158
|
+
authenticate(c: Context): Promise<StrategyResult>;
|
|
159
|
+
}
|
|
160
|
+
interface MagicLinkOptions {
|
|
161
|
+
name?: string;
|
|
162
|
+
/** Deliver the link. Called only when `verify` found a user for the address. */
|
|
163
|
+
send: (params: {
|
|
164
|
+
email: string;
|
|
165
|
+
url: string;
|
|
166
|
+
user: StrategyUser;
|
|
167
|
+
c: Context;
|
|
168
|
+
}) => Promise<void>;
|
|
169
|
+
/** Seconds a link stays valid (default 900). */
|
|
170
|
+
maxAge?: number;
|
|
171
|
+
emailField?: string;
|
|
172
|
+
/** Where forms go after asking for a link (default: the same URL with `?sent=1`). */
|
|
173
|
+
sentRedirect?: string;
|
|
174
|
+
}
|
|
175
|
+
type MagicLinkVerify = (params: {
|
|
176
|
+
email: string;
|
|
177
|
+
c: Context;
|
|
178
|
+
}) => Promise<StrategyUser | null>;
|
|
179
|
+
/**
|
|
180
|
+
* Passwordless login: `POST { email }` sends a link, `GET ?token=` logs in. Links expire after `maxAge` and stop
|
|
181
|
+
* working after one login (the signature covers `lastLogin`) or a password/email change. The POST answers the
|
|
182
|
+
* same whether or not the address has an account.
|
|
183
|
+
*/
|
|
184
|
+
declare class MagicLinkStrategy extends Strategy {
|
|
185
|
+
readonly options: MagicLinkOptions;
|
|
186
|
+
readonly verify: MagicLinkVerify;
|
|
187
|
+
readonly name: string;
|
|
188
|
+
constructor(options: MagicLinkOptions, verify: MagicLinkVerify);
|
|
189
|
+
/** A login token for `user` (exposed for custom delivery). */
|
|
190
|
+
makeToken(user: any): Promise<string>;
|
|
191
|
+
checkToken(token: string): Promise<StrategyUser | null>;
|
|
192
|
+
authenticate(c: Context, _options: AuthenticateOptions): Promise<StrategyResult>;
|
|
193
|
+
private request;
|
|
194
|
+
}
|
|
195
|
+
type BearerVerify = (token: string, c: Context) => Promise<StrategyUser | null | {
|
|
196
|
+
user: StrategyUser;
|
|
197
|
+
info?: Record<string, unknown>;
|
|
198
|
+
}>;
|
|
199
|
+
/** `Authorization: Bearer <token>` → `verify(token)`. No header → pass; unknown token → 401 with a challenge. */
|
|
200
|
+
declare class BearerStrategy extends Strategy {
|
|
201
|
+
readonly verify: BearerVerify;
|
|
202
|
+
readonly name: string;
|
|
203
|
+
private readonly realm;
|
|
204
|
+
constructor(verify: BearerVerify, options?: {
|
|
205
|
+
name?: string;
|
|
206
|
+
realm?: string;
|
|
207
|
+
});
|
|
208
|
+
authenticate(c: Context): Promise<StrategyResult>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface PendingFlow {
|
|
212
|
+
state: string;
|
|
213
|
+
strategy: string;
|
|
214
|
+
redirectUri: string;
|
|
215
|
+
verifier?: string;
|
|
216
|
+
nonce?: string;
|
|
217
|
+
next?: string;
|
|
218
|
+
extra?: Record<string, string>;
|
|
219
|
+
/** ms since epoch */
|
|
220
|
+
createdAt: number;
|
|
221
|
+
}
|
|
222
|
+
declare function stateSettings(): {
|
|
223
|
+
cookie: string;
|
|
224
|
+
maxAge: number;
|
|
225
|
+
};
|
|
226
|
+
/** Remember a flow until the callback (newest five kept). */
|
|
227
|
+
declare function savePending(c: Context, flow: PendingFlow): Promise<void>;
|
|
228
|
+
/** Take the flow for `state` (single use). Null when it is unknown or expired. */
|
|
229
|
+
declare function takePending(c: Context, state: string | undefined): Promise<PendingFlow | null>;
|
|
230
|
+
|
|
231
|
+
/** Resolve a setting that may be a function of env (and async, for computed secrets). */
|
|
232
|
+
declare function envValue<T>(value: EnvValue<T> | ((env: Record<string, any>) => Promise<T>)): Promise<T>;
|
|
233
|
+
interface VerifyParams {
|
|
234
|
+
profile: Profile;
|
|
235
|
+
tokens: OAuthTokens;
|
|
236
|
+
c: Context;
|
|
237
|
+
/** The flow this callback belongs to: `next`, `extra` (per-request state). */
|
|
238
|
+
flow: PendingFlow;
|
|
239
|
+
}
|
|
240
|
+
type VerifyResult = StrategyUser | null | false | {
|
|
241
|
+
user: StrategyUser;
|
|
242
|
+
info?: Record<string, unknown>;
|
|
243
|
+
};
|
|
244
|
+
type VerifyFunction = (params: VerifyParams) => Promise<VerifyResult>;
|
|
245
|
+
interface ProfileContext {
|
|
246
|
+
c: Context;
|
|
247
|
+
getJson(url: string, init?: RequestInit): Promise<Record<string, any>>;
|
|
248
|
+
/** GET with `Authorization: Bearer <access token>`. */
|
|
249
|
+
getJsonAuthed(url: string, init?: RequestInit): Promise<Record<string, any>>;
|
|
250
|
+
}
|
|
251
|
+
interface OAuth2StrategyOptions {
|
|
252
|
+
/** Strategy and profile provider name (default `'oauth2'`). */
|
|
253
|
+
name?: string;
|
|
254
|
+
authorizationURL: EnvValue<string>;
|
|
255
|
+
tokenURL: EnvValue<string>;
|
|
256
|
+
clientId: EnvValue<string>;
|
|
257
|
+
clientSecret?: EnvValue<string | undefined> | ((env: Record<string, any>) => Promise<string>);
|
|
258
|
+
/** Path or absolute URL of the callback (default: the path of the current request). */
|
|
259
|
+
callbackURL?: EnvValue<string>;
|
|
260
|
+
scope?: string[];
|
|
261
|
+
scopeSeparator?: string;
|
|
262
|
+
/** PKCE with S256 (default true). */
|
|
263
|
+
pkce?: boolean;
|
|
264
|
+
authorizationParams?: Record<string, string> | ((c: Context) => Record<string, string>);
|
|
265
|
+
tokenAuth?: 'body' | 'basic';
|
|
266
|
+
revocationURL?: EnvValue<string | undefined>;
|
|
267
|
+
/** Fetch the profile from this URL with the access token, unless `profile` is given. */
|
|
268
|
+
userInfoURL?: EnvValue<string>;
|
|
269
|
+
/** Build the normalized profile from the tokens. */
|
|
270
|
+
profile?: (tokens: OAuthTokens, ctx: ProfileContext) => Promise<Profile>;
|
|
271
|
+
}
|
|
272
|
+
/** Map a typical userinfo document to a `Profile`. */
|
|
273
|
+
declare function profileFromClaims(provider: string, raw: Record<string, any>): Profile;
|
|
274
|
+
/**
|
|
275
|
+
* OAuth 2.0 authorization code flow (with PKCE). Mount one view for both legs:
|
|
276
|
+
* `path('login/acme/', authenticate('acme'))` — without `code` it redirects to the provider, with it it logs in.
|
|
277
|
+
*/
|
|
278
|
+
declare class OAuth2Strategy extends Strategy {
|
|
279
|
+
readonly options: OAuth2StrategyOptions;
|
|
280
|
+
readonly verify: VerifyFunction;
|
|
281
|
+
readonly name: string;
|
|
282
|
+
constructor(options: OAuth2StrategyOptions, verify: VerifyFunction);
|
|
283
|
+
client(): Promise<OAuth2Client>;
|
|
284
|
+
callbackUrl(c: Context): Promise<string>;
|
|
285
|
+
/** Extra authorization parameters and flow fields; OIDC adds a nonce. */
|
|
286
|
+
protected startExtras(_c: Context): Promise<{
|
|
287
|
+
params?: Record<string, string>;
|
|
288
|
+
nonce?: string;
|
|
289
|
+
}>;
|
|
290
|
+
protected scopes(options: AuthenticateOptions): string[];
|
|
291
|
+
authenticate(c: Context, options: AuthenticateOptions): Promise<StrategyResult>;
|
|
292
|
+
protected start(c: Context, options: AuthenticateOptions): Promise<StrategyResult>;
|
|
293
|
+
protected callback(c: Context, code: string, state: string | undefined): Promise<StrategyResult>;
|
|
294
|
+
protected profileContext(c: Context, tokens: OAuthTokens): ProfileContext;
|
|
295
|
+
/** The normalized profile for these tokens. */
|
|
296
|
+
userProfile(tokens: OAuthTokens, c: Context, _flow?: PendingFlow): Promise<Profile>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
interface DiscoveryDocument {
|
|
300
|
+
issuer: string;
|
|
301
|
+
authorization_endpoint: string;
|
|
302
|
+
token_endpoint: string;
|
|
303
|
+
userinfo_endpoint?: string;
|
|
304
|
+
jwks_uri: string;
|
|
305
|
+
revocation_endpoint?: string;
|
|
306
|
+
end_session_endpoint?: string;
|
|
307
|
+
id_token_signing_alg_values_supported?: string[];
|
|
308
|
+
[k: string]: unknown;
|
|
309
|
+
}
|
|
310
|
+
/** Fetch (and cache for an hour) `<issuer>/.well-known/openid-configuration`. */
|
|
311
|
+
declare function discover(issuer: string): Promise<DiscoveryDocument>;
|
|
312
|
+
declare function clearDiscoveryCache(): void;
|
|
313
|
+
interface OIDCStrategyOptions extends Omit<OAuth2StrategyOptions, 'authorizationURL' | 'tokenURL' | 'userInfoURL' | 'name'> {
|
|
314
|
+
name?: string;
|
|
315
|
+
issuer: EnvValue<string>;
|
|
316
|
+
/** Skip discovery by giving the endpoints (any you leave out still come from discovery). */
|
|
317
|
+
endpoints?: Partial<Pick<DiscoveryDocument, 'authorization_endpoint' | 'token_endpoint' | 'userinfo_endpoint' | 'jwks_uri' | 'revocation_endpoint'>>;
|
|
318
|
+
/**
|
|
319
|
+
* How to check the ID token's `iss`: exact match with the issuer (default), or a predicate for multi-tenant
|
|
320
|
+
* issuers (Microsoft's `common` endpoint).
|
|
321
|
+
*/
|
|
322
|
+
issuerValidation?: (iss: string) => boolean;
|
|
323
|
+
/** Also call the userinfo endpoint and merge its claims (default true when the issuer has one). */
|
|
324
|
+
fetchUserInfo?: boolean;
|
|
325
|
+
algorithms?: readonly JwsAlgorithm[];
|
|
326
|
+
}
|
|
327
|
+
/** OpenID Connect login: OAuth2 + discovery + validated ID token. Default scope `openid email profile`. */
|
|
328
|
+
declare class OIDCStrategy extends OAuth2Strategy {
|
|
329
|
+
readonly options: OAuth2StrategyOptions & OIDCStrategyOptions;
|
|
330
|
+
constructor(options: OIDCStrategyOptions, verify: VerifyFunction);
|
|
331
|
+
discovery(): Promise<DiscoveryDocument>;
|
|
332
|
+
protected startExtras(_c: Context): Promise<{
|
|
333
|
+
nonce: string;
|
|
334
|
+
}>;
|
|
335
|
+
/** Validate an ID token from this provider (also used for tokens obtained by native SDKs). */
|
|
336
|
+
validateIdToken(idToken: string, nonce?: string): Promise<Record<string, any>>;
|
|
337
|
+
userProfile(tokens: OAuthTokens, c: Context, flow?: PendingFlow): Promise<Profile>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export { AuthenticateOptions, BearerStrategy, type BearerVerify, type DecodedJwt, type DiscoveryDocument, EnvValue, type JwsAlgorithm, LocalStrategy, type LocalStrategyOptions, type LocalVerify, type MagicLinkOptions, MagicLinkStrategy, type MagicLinkVerify, type NamedResult, OAuth2Client, type OAuth2ClientConfig, OAuth2Strategy, type OAuth2StrategyOptions, OAuthError, OAuthTokens, OIDCStrategy, type OIDCStrategyOptions, type PendingFlow, Profile, type ProfileContext, SUPPORTED_ALGORITHMS, Strategy, StrategyRef, StrategyResult, StrategyUser, TokenValidationError, type VerifyFunction, type VerifyOptions, type VerifyParams, type VerifyResult, authenticate, b64url, clearDiscoveryCache, clearJwksCache, completeLogin, decodeJwt, discover, envValue, failureResponse, fromB64url, getJson, importPkcs8, loginBackendName, pkcePair, profileFromClaims, providerFetch, randomToken, readBody, runStrategy, savePending, seal, setProviderFetch, sha256, signJwt, stateSettings, takePending, timingSafeEqual, toTokens, unseal, userInfo, utf8, verifyJwt };
|