@ministryofmany/client 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/dist/index.js ADDED
@@ -0,0 +1,231 @@
1
+ import {
2
+ MinisterTokenError,
3
+ OidcError,
4
+ VcVerificationError,
5
+ buildDid,
6
+ claimsFromPayload,
7
+ didFromIssuer,
8
+ verifyIdTokenPayload,
9
+ verifyMinisterBadge,
10
+ verifyMinisterBadges,
11
+ verifyMinisterIdToken
12
+ } from "./chunk-CSD6YO64.js";
13
+ import "./chunk-R4XGCZVA.js";
14
+ import {
15
+ AGE_THRESHOLDS,
16
+ AgeOverClaimsFor,
17
+ BADGE_TYPES,
18
+ EmailDomainClaims,
19
+ EmailExactClaims,
20
+ InviteCodeClaims,
21
+ OAUTH_PROVIDERS,
22
+ OAuthAccountClaims,
23
+ ResidencyCityClaims,
24
+ ResidencyCountryClaims,
25
+ ResidencyStateClaims,
26
+ TlsnAttestationClaims,
27
+ badgeScope,
28
+ badgeScopes,
29
+ badgeTypeOf,
30
+ defineBadgeType,
31
+ getBadgeClaimSchema,
32
+ knownBadgeTypes,
33
+ slugForCredentialType
34
+ } from "./chunk-U2JFQKFV.js";
35
+
36
+ // src/oidc.ts
37
+ import { createRemoteJWKSet } from "jose";
38
+ var discoveryCache = /* @__PURE__ */ new Map();
39
+ var idTokenJwksCache = /* @__PURE__ */ new Map();
40
+ async function discover(issuer) {
41
+ const cached = discoveryCache.get(issuer);
42
+ if (cached) return cached;
43
+ const p = fetch(`${issuer}/.well-known/openid-configuration`).then(async (res) => {
44
+ if (!res.ok) {
45
+ throw new OidcError(`OIDC discovery failed: HTTP ${res.status}`);
46
+ }
47
+ const doc = await res.json();
48
+ if (doc.issuer?.replace(/\/$/, "") !== issuer.replace(/\/$/, "")) {
49
+ throw new OidcError(
50
+ `OIDC discovery issuer mismatch: configured ${issuer}, document ${doc.issuer}`
51
+ );
52
+ }
53
+ return doc;
54
+ }).catch((cause) => {
55
+ discoveryCache.delete(issuer);
56
+ throw cause instanceof OidcError ? cause : new OidcError(
57
+ `OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`
58
+ );
59
+ });
60
+ discoveryCache.set(issuer, p);
61
+ return p;
62
+ }
63
+ function idTokenJwks(issuer, jwksUri) {
64
+ let set = idTokenJwksCache.get(issuer);
65
+ if (!set) {
66
+ set = createRemoteJWKSet(new URL(jwksUri));
67
+ idTokenJwksCache.set(issuer, set);
68
+ }
69
+ return set;
70
+ }
71
+ var OidcCore = class {
72
+ issuer;
73
+ clientId;
74
+ clientSecret;
75
+ redirectUri;
76
+ constructor(config) {
77
+ if (!config.issuer) throw new OidcError("issuer is required");
78
+ if (!config.clientId) throw new OidcError("clientId is required");
79
+ if (!config.redirectUri) throw new OidcError("redirectUri is required");
80
+ this.issuer = config.issuer.replace(/\/$/, "");
81
+ this.clientId = config.clientId;
82
+ this.clientSecret = config.clientSecret;
83
+ this.redirectUri = config.redirectUri;
84
+ }
85
+ // Discover the authorization endpoint and build the redirect URL with
86
+ // caller-supplied scopes and PKCE S256.
87
+ async getAuthorizationUrl(args) {
88
+ if (args.scopes.length === 0) {
89
+ throw new OidcError("at least one scope is required");
90
+ }
91
+ const d = await discover(this.issuer);
92
+ const u = new URL(d.authorization_endpoint);
93
+ u.searchParams.set("response_type", "code");
94
+ u.searchParams.set("client_id", this.clientId);
95
+ u.searchParams.set("redirect_uri", this.redirectUri);
96
+ u.searchParams.set("scope", args.scopes.join(" "));
97
+ u.searchParams.set("state", args.state);
98
+ u.searchParams.set("nonce", args.nonce);
99
+ u.searchParams.set("code_challenge", args.codeChallenge);
100
+ u.searchParams.set("code_challenge_method", "S256");
101
+ for (const [key, value] of Object.entries(args.extraParams ?? {})) {
102
+ if (u.searchParams.has(key)) continue;
103
+ u.searchParams.set(key, value);
104
+ }
105
+ return u.toString();
106
+ }
107
+ // Exchange the authorization code for tokens, verify the id_token
108
+ // (signature via JWKS, iss/aud/nonce), then extract and verify each
109
+ // disclosed badge. Throws on any failure — the caller maps that to a
110
+ // 401.
111
+ async exchangeCode(args) {
112
+ const d = await discover(this.issuer);
113
+ const body = new URLSearchParams({
114
+ grant_type: "authorization_code",
115
+ code: args.code,
116
+ redirect_uri: this.redirectUri,
117
+ client_id: this.clientId,
118
+ code_verifier: args.codeVerifier
119
+ });
120
+ if (this.clientSecret) body.set("client_secret", this.clientSecret);
121
+ const res = await fetch(d.token_endpoint, {
122
+ method: "POST",
123
+ headers: { "content-type": "application/x-www-form-urlencoded" },
124
+ body
125
+ });
126
+ if (!res.ok) {
127
+ const detail = await res.text().catch(() => "");
128
+ throw new OidcError(`token exchange failed: HTTP ${res.status} ${detail}`);
129
+ }
130
+ const tokens = await res.json();
131
+ if (typeof tokens.id_token !== "string") {
132
+ throw new OidcError("token response missing id_token");
133
+ }
134
+ const idKey = args.idTokenKey ?? idTokenJwks(this.issuer, d.jwks_uri);
135
+ const payload = await verifyIdTokenPayload(tokens.id_token, {
136
+ issuer: d.issuer,
137
+ clientId: this.clientId,
138
+ nonce: args.expectedNonce,
139
+ key: idKey
140
+ });
141
+ const claims = claimsFromPayload(payload, tokens.id_token);
142
+ const { badges, rejected } = await verifyMinisterBadges(payload, {
143
+ issuer: this.issuer,
144
+ key: args.badgeKey
145
+ });
146
+ return { claims, badges, rejected };
147
+ }
148
+ };
149
+
150
+ // src/pkce.ts
151
+ function b64url(bytes) {
152
+ let str = "";
153
+ for (const b of bytes) str += String.fromCharCode(b);
154
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
155
+ }
156
+ function randomBytes(length) {
157
+ const buf = new Uint8Array(length);
158
+ crypto.getRandomValues(buf);
159
+ return buf;
160
+ }
161
+ async function sha256(input) {
162
+ const data = new TextEncoder().encode(input);
163
+ const digest = await crypto.subtle.digest("SHA-256", data);
164
+ return new Uint8Array(digest);
165
+ }
166
+ async function generatePkce() {
167
+ const verifier = b64url(randomBytes(32));
168
+ const challenge = b64url(await sha256(verifier));
169
+ return { verifier, challenge };
170
+ }
171
+ function randomUrlToken(bytes = 16) {
172
+ return b64url(randomBytes(bytes));
173
+ }
174
+
175
+ // src/client.ts
176
+ function createMinisterClient(config) {
177
+ const core = new OidcCore(config);
178
+ const issuer = config.issuer.replace(/\/$/, "");
179
+ return {
180
+ getAuthorizationUrl: (args) => core.getAuthorizationUrl(args),
181
+ exchangeCode: (args) => core.exchangeCode(args),
182
+ verifyMinisterBadge: (vcJwt, options) => verifyMinisterBadge(vcJwt, { issuer, key: options?.key }),
183
+ generatePkce: () => generatePkce(),
184
+ randomToken: (bytes) => randomUrlToken(bytes),
185
+ badgeScope: (slug) => badgeScope(slug)
186
+ };
187
+ }
188
+
189
+ // src/verifier.ts
190
+ function createMinisterVerifier(config) {
191
+ const { issuer, clientId, jwks } = config;
192
+ return {
193
+ verifyIdToken: (idToken, opts) => verifyMinisterIdToken(idToken, { issuer, clientId, key: jwks, nonce: opts?.nonce }),
194
+ verifyBadges: (tokenOrPayload) => verifyMinisterBadges(tokenOrPayload, { issuer, clientId, key: jwks }),
195
+ verifyBadge: (vcJwt) => verifyMinisterBadge(vcJwt, { issuer, key: jwks })
196
+ };
197
+ }
198
+ export {
199
+ AGE_THRESHOLDS,
200
+ AgeOverClaimsFor,
201
+ BADGE_TYPES,
202
+ EmailDomainClaims,
203
+ EmailExactClaims,
204
+ InviteCodeClaims,
205
+ MinisterTokenError,
206
+ OAUTH_PROVIDERS,
207
+ OAuthAccountClaims,
208
+ OidcError,
209
+ ResidencyCityClaims,
210
+ ResidencyCountryClaims,
211
+ ResidencyStateClaims,
212
+ TlsnAttestationClaims,
213
+ VcVerificationError,
214
+ badgeScope,
215
+ badgeScopes,
216
+ badgeTypeOf,
217
+ buildDid,
218
+ createMinisterClient,
219
+ createMinisterVerifier,
220
+ defineBadgeType,
221
+ didFromIssuer,
222
+ generatePkce,
223
+ getBadgeClaimSchema,
224
+ knownBadgeTypes,
225
+ randomUrlToken,
226
+ slugForCredentialType,
227
+ verifyMinisterBadge,
228
+ verifyMinisterBadges,
229
+ verifyMinisterIdToken
230
+ };
231
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/oidc.ts","../src/pkce.ts","../src/client.ts","../src/verifier.ts"],"sourcesContent":["import { createRemoteJWKSet } from \"jose\";\n\nimport { OidcError } from \"./errors\";\nimport { verifyIdTokenPayload, claimsFromPayload } from \"./verify-id-token\";\nimport { verifyMinisterBadges } from \"./verify-badges\";\nimport type {\n ExchangeResult,\n KeyInput,\n MinisterClientConfig,\n} from \"./types\";\n\n// The fields of the OIDC discovery document this SDK relies on.\ninterface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n}\n\n// Cache the discovery doc + id_token JWKS per issuer for the process\n// lifetime. The JWKS set fetches lazily and rotates keys on its own.\nconst discoveryCache = new Map<string, Promise<Discovery>>();\nconst idTokenJwksCache = new Map<\n string,\n ReturnType<typeof createRemoteJWKSet>\n>();\n\nasync function discover(issuer: string): Promise<Discovery> {\n const cached = discoveryCache.get(issuer);\n if (cached) return cached;\n const p = fetch(`${issuer}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) {\n throw new OidcError(`OIDC discovery failed: HTTP ${res.status}`);\n }\n const doc = (await res.json()) as Discovery;\n if (doc.issuer?.replace(/\\/$/, \"\") !== issuer.replace(/\\/$/, \"\")) {\n throw new OidcError(\n `OIDC discovery issuer mismatch: configured ${issuer}, document ${doc.issuer}`,\n );\n }\n return doc;\n })\n .catch((cause) => {\n // Don't poison the cache with a rejected promise.\n discoveryCache.delete(issuer);\n throw cause instanceof OidcError\n ? cause\n : new OidcError(\n `OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n );\n });\n discoveryCache.set(issuer, p);\n return p;\n}\n\nfunction idTokenJwks(\n issuer: string,\n jwksUri: string,\n): ReturnType<typeof createRemoteJWKSet> {\n let set = idTokenJwksCache.get(issuer);\n if (!set) {\n set = createRemoteJWKSet(new URL(jwksUri));\n idTokenJwksCache.set(issuer, set);\n }\n return set;\n}\n\nexport interface GetAuthorizationUrlArgs {\n // Requested scopes, e.g. [\"openid\", \"profile\", \"badge:age-over-21\"].\n // `openid` is required by OIDC; this SDK does not inject it for you.\n scopes: string[];\n state: string;\n nonce: string;\n // PKCE S256 code challenge (from `generatePkce().challenge`).\n codeChallenge: string;\n // Extra authorize-request params appended alongside the standard ones\n // (e.g. `{ minister_policy: \"<b64url>\" }`). Keys that collide with a\n // standard param the SDK sets (response_type, client_id, redirect_uri,\n // scope, state, nonce, code_challenge, code_challenge_method) are\n // ignored, so it can never clobber them. Optional; omitting it is a\n // no-op.\n extraParams?: Record<string, string>;\n}\n\nexport interface ExchangeCodeArgs {\n // The `code` query param from the callback.\n code: string;\n // The PKCE verifier persisted from flow start.\n codeVerifier: string;\n // The `nonce` persisted from flow start; the verified id_token's\n // `nonce` must equal this.\n expectedNonce: string;\n // Inject the id_token verification key source (defaults to the remote\n // JWKS at the discovery `jwks_uri`). Tests pass a public key so\n // verification never touches the network.\n idTokenKey?: KeyInput;\n // Inject the badge verification key source (defaults to the remote\n // JWKS at `${issuer}/.well-known/jwks.json`).\n badgeKey?: KeyInput;\n}\n\n// Internal: the OIDC operations bound to a normalized config.\nexport class OidcCore {\n private readonly issuer: string;\n private readonly clientId: string;\n private readonly clientSecret?: string;\n private readonly redirectUri: string;\n\n constructor(config: MinisterClientConfig) {\n if (!config.issuer) throw new OidcError(\"issuer is required\");\n if (!config.clientId) throw new OidcError(\"clientId is required\");\n if (!config.redirectUri) throw new OidcError(\"redirectUri is required\");\n this.issuer = config.issuer.replace(/\\/$/, \"\");\n this.clientId = config.clientId;\n this.clientSecret = config.clientSecret;\n this.redirectUri = config.redirectUri;\n }\n\n // Discover the authorization endpoint and build the redirect URL with\n // caller-supplied scopes and PKCE S256.\n async getAuthorizationUrl(args: GetAuthorizationUrlArgs): Promise<string> {\n if (args.scopes.length === 0) {\n throw new OidcError(\"at least one scope is required\");\n }\n const d = await discover(this.issuer);\n const u = new URL(d.authorization_endpoint);\n u.searchParams.set(\"response_type\", \"code\");\n u.searchParams.set(\"client_id\", this.clientId);\n u.searchParams.set(\"redirect_uri\", this.redirectUri);\n u.searchParams.set(\"scope\", args.scopes.join(\" \"));\n u.searchParams.set(\"state\", args.state);\n u.searchParams.set(\"nonce\", args.nonce);\n u.searchParams.set(\"code_challenge\", args.codeChallenge);\n u.searchParams.set(\"code_challenge_method\", \"S256\");\n // Append caller-supplied extra params last, but never let one\n // override a standard param the SDK just set.\n for (const [key, value] of Object.entries(args.extraParams ?? {})) {\n if (u.searchParams.has(key)) continue;\n u.searchParams.set(key, value);\n }\n return u.toString();\n }\n\n // Exchange the authorization code for tokens, verify the id_token\n // (signature via JWKS, iss/aud/nonce), then extract and verify each\n // disclosed badge. Throws on any failure — the caller maps that to a\n // 401.\n async exchangeCode(args: ExchangeCodeArgs): Promise<ExchangeResult> {\n const d = await discover(this.issuer);\n\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code: args.code,\n redirect_uri: this.redirectUri,\n client_id: this.clientId,\n code_verifier: args.codeVerifier,\n });\n if (this.clientSecret) body.set(\"client_secret\", this.clientSecret);\n\n const res = await fetch(d.token_endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new OidcError(`token exchange failed: HTTP ${res.status} ${detail}`);\n }\n const tokens = (await res.json()) as { id_token?: unknown };\n if (typeof tokens.id_token !== \"string\") {\n throw new OidcError(\"token response missing id_token\");\n }\n\n const idKey = args.idTokenKey ?? idTokenJwks(this.issuer, d.jwks_uri);\n const payload = await verifyIdTokenPayload(tokens.id_token, {\n issuer: d.issuer,\n clientId: this.clientId,\n nonce: args.expectedNonce,\n key: idKey,\n });\n const claims = claimsFromPayload(payload, tokens.id_token);\n const { badges, rejected } = await verifyMinisterBadges(payload, {\n issuer: this.issuer,\n key: args.badgeKey,\n });\n return { claims, badges, rejected };\n }\n}\n\n// Test seam: clear the discovery + id_token JWKS caches.\nexport function _resetOidcCaches(issuer?: string): void {\n if (issuer) {\n const normalized = issuer.replace(/\\/$/, \"\");\n discoveryCache.delete(normalized);\n idTokenJwksCache.delete(normalized);\n } else {\n discoveryCache.clear();\n idTokenJwksCache.clear();\n }\n}\n","import type { PkcePair } from \"./types\";\n\n// PKCE + random-token helpers built on the Web Crypto API\n// (`globalThis.crypto`), so the SDK runs unchanged on Node 20+, Deno,\n// and edge runtimes (Vercel Edge, Cloudflare Workers) — the same\n// environments jose targets. No `node:crypto` import.\n\nfunction b64url(bytes: Uint8Array): string {\n let str = \"\";\n for (const b of bytes) str += String.fromCharCode(b);\n return btoa(str).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/u, \"\");\n}\n\nfunction randomBytes(length: number): Uint8Array {\n const buf = new Uint8Array(length);\n crypto.getRandomValues(buf);\n return buf;\n}\n\nasync function sha256(input: string): Promise<Uint8Array> {\n const data = new TextEncoder().encode(input);\n const digest = await crypto.subtle.digest(\"SHA-256\", data);\n return new Uint8Array(digest);\n}\n\n// Generate a PKCE verifier/challenge pair (RFC 7636, S256). The verifier\n// is 32 random bytes base64url-encoded; the challenge is its SHA-256.\nexport async function generatePkce(): Promise<PkcePair> {\n const verifier = b64url(randomBytes(32));\n const challenge = b64url(await sha256(verifier));\n return { verifier, challenge };\n}\n\n// A URL-safe random token, used for `state` and `nonce`. 16 bytes\n// (128 bits) of entropy by default.\nexport function randomUrlToken(bytes = 16): string {\n return b64url(randomBytes(bytes));\n}\n","import { badgeScope } from \"./badges\";\nimport {\n OidcCore,\n type ExchangeCodeArgs,\n type GetAuthorizationUrlArgs,\n} from \"./oidc\";\nimport { generatePkce, randomUrlToken } from \"./pkce\";\nimport type {\n ExchangeResult,\n KeyInput,\n MinisterClientConfig,\n PkcePair,\n} from \"./types\";\nimport { verifyMinisterBadge } from \"./verify-badge\";\n\n// The relying-party client surface returned by createMinisterClient.\nexport interface MinisterClient {\n // Discover the authorization endpoint and build the redirect URL.\n // Pass the scopes you want, e.g. [\"openid\",\"profile\",\"badge:age-over-21\"].\n getAuthorizationUrl(args: GetAuthorizationUrlArgs): Promise<string>;\n\n // Exchange the callback `code` for verified id_token claims plus\n // signature-verified, holder-bound badges.\n exchangeCode(args: ExchangeCodeArgs): Promise<ExchangeResult>;\n\n // Verify a single received VC badge against Minister's public keys.\n // Useful for badges received out of band (e.g. share links), not just\n // those returned from exchangeCode. The client supplies its own issuer.\n verifyMinisterBadge(\n vcJwt: string,\n options?: { key?: KeyInput },\n ): ReturnType<typeof verifyMinisterBadge>;\n\n // PKCE S256 pair. Keep `verifier` server-side; put `challenge` in the\n // auth URL.\n generatePkce(): Promise<PkcePair>;\n\n // 128-bit URL-safe random token for `state` / `nonce`.\n randomToken(bytes?: number): string;\n\n // Build a `badge:<slug>` scope string.\n badgeScope(slug: string): string;\n}\n\n// Create a Minister relying-party client. `issuer` is Minister's origin\n// (e.g. \"https://ministry.id\"). The SDK stores no state: persistence of\n// the per-request OidcFlowState is the app's responsibility.\nexport function createMinisterClient(\n config: MinisterClientConfig,\n): MinisterClient {\n const core = new OidcCore(config);\n const issuer = config.issuer.replace(/\\/$/, \"\");\n\n return {\n getAuthorizationUrl: (args) => core.getAuthorizationUrl(args),\n exchangeCode: (args) => core.exchangeCode(args),\n verifyMinisterBadge: (vcJwt, options) =>\n verifyMinisterBadge(vcJwt, { issuer, key: options?.key }),\n generatePkce: () => generatePkce(),\n randomToken: (bytes) => randomUrlToken(bytes),\n badgeScope: (slug) => badgeScope(slug),\n };\n}\n","import { verifyMinisterIdToken } from \"./verify-id-token\";\nimport { verifyMinisterBadges } from \"./verify-badges\";\nimport { verifyMinisterBadge } from \"./verify-badge\";\nimport type { JWTPayload } from \"jose\";\nimport type { KeyInput, MinisterClaims, VerifiedBadge, BadgesResult } from \"./types\";\n\nexport interface MinisterVerifierConfig {\n // Minister origin, e.g. \"https://ministry.id\".\n issuer: string;\n // When set, id_token `aud` is checked against it. Recommended.\n clientId?: string;\n // Inject the verification key. When omitted, each verifier fetches\n // Minister's remote JWKS on demand. Accepts a single public key too;\n // pass a public JWK in tests so verification stays offline.\n jwks?: KeyInput;\n}\n\nexport interface MinisterVerifier {\n verifyIdToken(idToken: string, opts?: { nonce?: string }): Promise<MinisterClaims>;\n verifyBadges(tokenOrPayload: string | JWTPayload): Promise<BadgesResult>;\n verifyBadge(vcJwt: string): Promise<VerifiedBadge>;\n}\n\n// Configure-once, reuse: binds issuer/clientId/key so the three operations\n// share one configuration. When `jwks` is injected, all three use that exact\n// key. When omitted, each underlying verifier keeps its OWN per-issuer\n// remote-JWKS cache (the id_token and badge verifiers cache independently -\n// they do NOT share a JWKSet), so expect at most one fetch per verifier per\n// issuer, not a single shared fetch.\nexport function createMinisterVerifier(config: MinisterVerifierConfig): MinisterVerifier {\n const { issuer, clientId, jwks } = config;\n return {\n verifyIdToken: (idToken, opts) =>\n verifyMinisterIdToken(idToken, { issuer, clientId, key: jwks, nonce: opts?.nonce }),\n verifyBadges: (tokenOrPayload) =>\n verifyMinisterBadges(tokenOrPayload, { issuer, clientId, key: jwks }),\n verifyBadge: (vcJwt) => verifyMinisterBadge(vcJwt, { issuer, key: jwks }),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,56 @@
1
+ import { KeyLike, JWTVerifyGetKey } from 'jose';
2
+
3
+ declare class VcVerificationError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ declare class OidcError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ declare class MinisterTokenError extends Error {
10
+ constructor(message: string);
11
+ }
12
+
13
+ interface MinisterClientConfig {
14
+ issuer: string;
15
+ clientId: string;
16
+ clientSecret?: string;
17
+ redirectUri: string;
18
+ }
19
+ interface PkcePair {
20
+ verifier: string;
21
+ challenge: string;
22
+ }
23
+ interface OidcFlowState {
24
+ state: string;
25
+ nonce: string;
26
+ codeVerifier: string;
27
+ expiresAt: number;
28
+ }
29
+ interface MinisterClaims {
30
+ sub: string;
31
+ name?: string;
32
+ picture?: string;
33
+ raw: string;
34
+ }
35
+ interface VerifiedBadge {
36
+ type: string;
37
+ claims: Record<string, unknown>;
38
+ subject: string;
39
+ raw: string;
40
+ }
41
+ interface RejectedBadge {
42
+ raw: string;
43
+ error: VcVerificationError;
44
+ }
45
+ interface BadgesResult {
46
+ badges: VerifiedBadge[];
47
+ rejected: RejectedBadge[];
48
+ }
49
+ interface ExchangeResult {
50
+ claims: MinisterClaims;
51
+ badges: VerifiedBadge[];
52
+ rejected: RejectedBadge[];
53
+ }
54
+ type KeyInput = KeyLike | Uint8Array | JWTVerifyGetKey;
55
+
56
+ export { type BadgesResult as B, type ExchangeResult as E, type KeyInput as K, type MinisterClientConfig as M, OidcError as O, type PkcePair as P, type RejectedBadge as R, type VerifiedBadge as V, type MinisterClaims as a, MinisterTokenError as b, type OidcFlowState as c, VcVerificationError as d };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@ministryofmany/client",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for OIDC relying parties authenticating users via Minister and consuming W3C verifiable-credential badges.",
5
+ "type": "module",
6
+ "license": "MIT OR Apache-2.0",
7
+ "author": "AtHeartEngineer",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./auth-js": {
17
+ "types": "./dist/auth-js.d.ts",
18
+ "import": "./dist/auth-js.js"
19
+ },
20
+ "./badges": {
21
+ "types": "./dist/badges/index.d.ts",
22
+ "import": "./dist/badges/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "sideEffects": false,
31
+ "dependencies": {
32
+ "jose": "^5.9.6",
33
+ "zod": "^3.24.1"
34
+ },
35
+ "peerDependencies": {
36
+ "@auth/core": "^0.37.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@auth/core": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@auth/core": "^0.37.0",
45
+ "tsup": "^8.3.5",
46
+ "typescript": "^5.6.3",
47
+ "vitest": "^2.1.8"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "typecheck": "tsc --noEmit",
55
+ "test": "vitest run"
56
+ }
57
+ }