@dloizides/auth-client 1.0.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 +18 -0
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/index.d.mts +418 -0
- package/dist/index.d.ts +418 -0
- package/dist/index.js +463 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +440 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +76 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// src/utils/buildKeycloakUrls.ts
|
|
2
|
+
var REALM_PATH_PREFIX = "/realms";
|
|
3
|
+
var PROTOCOL_PATH = "/protocol/openid-connect";
|
|
4
|
+
function trimTrailingSlash(value) {
|
|
5
|
+
return value.replace(/\/$/, "");
|
|
6
|
+
}
|
|
7
|
+
function buildIssuerUrl(baseUrl, realm) {
|
|
8
|
+
return `${trimTrailingSlash(baseUrl)}${REALM_PATH_PREFIX}/${encodeURIComponent(realm)}`;
|
|
9
|
+
}
|
|
10
|
+
function buildAuthorizationEndpoint(baseUrl, realm) {
|
|
11
|
+
return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/auth`;
|
|
12
|
+
}
|
|
13
|
+
function buildTokenEndpoint(baseUrl, realm) {
|
|
14
|
+
return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/token`;
|
|
15
|
+
}
|
|
16
|
+
function buildUserInfoEndpoint(baseUrl, realm) {
|
|
17
|
+
return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/userinfo`;
|
|
18
|
+
}
|
|
19
|
+
function buildLogoutEndpoint(baseUrl, realm) {
|
|
20
|
+
return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/logout`;
|
|
21
|
+
}
|
|
22
|
+
function buildAuthorizationUrl(input) {
|
|
23
|
+
const params = new URLSearchParams({
|
|
24
|
+
client_id: input.clientId,
|
|
25
|
+
redirect_uri: input.redirectUri,
|
|
26
|
+
response_type: "code"
|
|
27
|
+
});
|
|
28
|
+
if (typeof input.scope === "string" && input.scope !== "") {
|
|
29
|
+
params.set("scope", input.scope);
|
|
30
|
+
}
|
|
31
|
+
if (typeof input.state === "string" && input.state !== "") {
|
|
32
|
+
params.set("state", input.state);
|
|
33
|
+
}
|
|
34
|
+
if (typeof input.codeChallenge === "string" && input.codeChallenge !== "") {
|
|
35
|
+
params.set("code_challenge", input.codeChallenge);
|
|
36
|
+
params.set("code_challenge_method", input.codeChallengeMethod ?? "S256");
|
|
37
|
+
}
|
|
38
|
+
return `${buildAuthorizationEndpoint(input.baseUrl, input.realm)}?${params.toString()}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils/isTokenExpired.ts
|
|
42
|
+
var SECONDS_TO_MILLIS = 1e3;
|
|
43
|
+
var DEFAULT_LEEWAY_MS = 30 * SECONDS_TO_MILLIS;
|
|
44
|
+
function isTokenExpired(tokens, leewayMs = DEFAULT_LEEWAY_MS, now = Date.now()) {
|
|
45
|
+
if (!tokens) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (typeof tokens.expiresAt !== "number" || tokens.expiresAt <= 0) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return tokens.expiresAt - leewayMs <= now;
|
|
52
|
+
}
|
|
53
|
+
function computeExpiresAt(expiresInSeconds, now = Date.now()) {
|
|
54
|
+
if (typeof expiresInSeconds !== "number" || expiresInSeconds <= 0) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
return now + expiresInSeconds * SECONDS_TO_MILLIS;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/utils/parseRealmFromIssuer.ts
|
|
61
|
+
function parseRealmFromIssuer(issuerUrl) {
|
|
62
|
+
if (typeof issuerUrl !== "string" || issuerUrl === "") {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const match = /\/realms\/([^/?#]+)/i.exec(issuerUrl);
|
|
66
|
+
if (!match || match[1] === void 0 || match[1] === "") {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return decodeURIComponent(match[1]);
|
|
70
|
+
}
|
|
71
|
+
function parseBaseUrlFromIssuer(issuerUrl) {
|
|
72
|
+
if (typeof issuerUrl !== "string" || issuerUrl === "") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const idx = issuerUrl.search(/\/realms\//i);
|
|
76
|
+
if (idx === -1) {
|
|
77
|
+
return issuerUrl.replace(/\/$/, "");
|
|
78
|
+
}
|
|
79
|
+
return issuerUrl.substring(0, idx).replace(/\/$/, "");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/AuthClient.ts
|
|
83
|
+
var DEFAULT_SCOPE = "openid profile email";
|
|
84
|
+
var AuthClient = class _AuthClient {
|
|
85
|
+
/**
|
|
86
|
+
* @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.
|
|
87
|
+
*/
|
|
88
|
+
constructor(config, storage) {
|
|
89
|
+
_AuthClient.validateConfig(config);
|
|
90
|
+
this.config = {
|
|
91
|
+
...config,
|
|
92
|
+
scope: config.scope ?? DEFAULT_SCOPE
|
|
93
|
+
};
|
|
94
|
+
this.tokenStorage = storage;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build an {@link AuthClient} from a standalone issuer URL by parsing the
|
|
98
|
+
* realm and base URL. Useful when migrating from the legacy
|
|
99
|
+
* `KEYCLOAK_ISSUER` env var convention.
|
|
100
|
+
*
|
|
101
|
+
* @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.
|
|
102
|
+
*/
|
|
103
|
+
static fromIssuerUrl(input, storage) {
|
|
104
|
+
const realm = parseRealmFromIssuer(input.issuerUrl);
|
|
105
|
+
const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);
|
|
106
|
+
if (realm === null || baseUrl === null || baseUrl === "") {
|
|
107
|
+
throw new Error(`AuthClient.fromIssuerUrl: cannot parse realm from "${input.issuerUrl}"`);
|
|
108
|
+
}
|
|
109
|
+
return new _AuthClient(
|
|
110
|
+
{
|
|
111
|
+
baseUrl,
|
|
112
|
+
realm,
|
|
113
|
+
clientId: input.clientId,
|
|
114
|
+
redirectUri: input.redirectUri,
|
|
115
|
+
scope: input.scope
|
|
116
|
+
},
|
|
117
|
+
storage
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
static validateConfig(config) {
|
|
121
|
+
if (typeof config.baseUrl !== "string" || config.baseUrl === "") {
|
|
122
|
+
throw new Error("AuthClient: baseUrl is required");
|
|
123
|
+
}
|
|
124
|
+
if (typeof config.realm !== "string" || config.realm === "") {
|
|
125
|
+
throw new Error("AuthClient: realm is required");
|
|
126
|
+
}
|
|
127
|
+
if (typeof config.clientId !== "string" || config.clientId === "") {
|
|
128
|
+
throw new Error("AuthClient: clientId is required");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
get realm() {
|
|
132
|
+
return this.config.realm;
|
|
133
|
+
}
|
|
134
|
+
get clientId() {
|
|
135
|
+
return this.config.clientId;
|
|
136
|
+
}
|
|
137
|
+
get baseUrl() {
|
|
138
|
+
return this.config.baseUrl.replace(/\/$/, "");
|
|
139
|
+
}
|
|
140
|
+
get scope() {
|
|
141
|
+
return this.config.scope;
|
|
142
|
+
}
|
|
143
|
+
get redirectUri() {
|
|
144
|
+
return this.config.redirectUri;
|
|
145
|
+
}
|
|
146
|
+
/** Issuer URL: `{baseUrl}/realms/{realm}`. */
|
|
147
|
+
get issuerUrl() {
|
|
148
|
+
return buildIssuerUrl(this.baseUrl, this.realm);
|
|
149
|
+
}
|
|
150
|
+
get authorizationEndpoint() {
|
|
151
|
+
return buildAuthorizationEndpoint(this.baseUrl, this.realm);
|
|
152
|
+
}
|
|
153
|
+
get tokenEndpoint() {
|
|
154
|
+
return buildTokenEndpoint(this.baseUrl, this.realm);
|
|
155
|
+
}
|
|
156
|
+
get userInfoEndpoint() {
|
|
157
|
+
return buildUserInfoEndpoint(this.baseUrl, this.realm);
|
|
158
|
+
}
|
|
159
|
+
get logoutEndpoint() {
|
|
160
|
+
return buildLogoutEndpoint(this.baseUrl, this.realm);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Build a fully-formed authorization URL the user agent can navigate to.
|
|
164
|
+
*
|
|
165
|
+
* @throws Error when `redirectUri` is not configured.
|
|
166
|
+
*/
|
|
167
|
+
buildAuthorizationUrl(input = {}) {
|
|
168
|
+
if (typeof this.config.redirectUri !== "string" || this.config.redirectUri === "") {
|
|
169
|
+
throw new Error("AuthClient.buildAuthorizationUrl: redirectUri is required");
|
|
170
|
+
}
|
|
171
|
+
return buildAuthorizationUrl({
|
|
172
|
+
baseUrl: this.baseUrl,
|
|
173
|
+
realm: this.realm,
|
|
174
|
+
clientId: this.clientId,
|
|
175
|
+
redirectUri: this.config.redirectUri,
|
|
176
|
+
scope: this.scope,
|
|
177
|
+
state: input.state,
|
|
178
|
+
codeChallenge: input.codeChallenge,
|
|
179
|
+
codeChallengeMethod: input.codeChallengeMethod
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async getTokens() {
|
|
183
|
+
return this.tokenStorage.read();
|
|
184
|
+
}
|
|
185
|
+
async setTokens(tokens) {
|
|
186
|
+
return this.tokenStorage.write(tokens);
|
|
187
|
+
}
|
|
188
|
+
async clearTokens() {
|
|
189
|
+
return this.tokenStorage.clear();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Read the current access token if it exists and is not expired.
|
|
193
|
+
* Returns `null` for "no usable token".
|
|
194
|
+
*/
|
|
195
|
+
async getAccessToken(now = Date.now()) {
|
|
196
|
+
const tokens = await this.tokenStorage.read();
|
|
197
|
+
if (tokens === null) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (isTokenExpired(tokens, void 0, now)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return tokens.accessToken;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/types/KeycloakRoles.ts
|
|
208
|
+
var KeycloakRoles = /* @__PURE__ */ ((KeycloakRoles2) => {
|
|
209
|
+
KeycloakRoles2["SuperUser"] = "superUser";
|
|
210
|
+
KeycloakRoles2["Admin"] = "admin";
|
|
211
|
+
KeycloakRoles2["User"] = "user";
|
|
212
|
+
return KeycloakRoles2;
|
|
213
|
+
})(KeycloakRoles || {});
|
|
214
|
+
var KEYCLOAK_ROLE_VALUES = [
|
|
215
|
+
"superUser" /* SuperUser */,
|
|
216
|
+
"admin" /* Admin */,
|
|
217
|
+
"user" /* User */
|
|
218
|
+
];
|
|
219
|
+
function isKeycloakRole(value) {
|
|
220
|
+
return KEYCLOAK_ROLE_VALUES.includes(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/storage/BrowserStorageTokenStorage.ts
|
|
224
|
+
var DEFAULT_KEY = "auth.tokens";
|
|
225
|
+
function isAuthTokens(value) {
|
|
226
|
+
if (typeof value !== "object" || value === null) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const candidate = value;
|
|
230
|
+
return typeof candidate.accessToken === "string" && typeof candidate.expiresAt === "number";
|
|
231
|
+
}
|
|
232
|
+
var BrowserStorageTokenStorage = class {
|
|
233
|
+
constructor(options) {
|
|
234
|
+
this.storage = options.storage;
|
|
235
|
+
this.key = options.key ?? DEFAULT_KEY;
|
|
236
|
+
}
|
|
237
|
+
read() {
|
|
238
|
+
return Promise.resolve(this.readSync());
|
|
239
|
+
}
|
|
240
|
+
write(tokens) {
|
|
241
|
+
this.storage.setItem(this.key, JSON.stringify(tokens));
|
|
242
|
+
return Promise.resolve();
|
|
243
|
+
}
|
|
244
|
+
clear() {
|
|
245
|
+
this.storage.removeItem(this.key);
|
|
246
|
+
return Promise.resolve();
|
|
247
|
+
}
|
|
248
|
+
readSync() {
|
|
249
|
+
try {
|
|
250
|
+
const raw = this.storage.getItem(this.key);
|
|
251
|
+
if (raw === null || raw === "") {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const parsed = JSON.parse(raw);
|
|
255
|
+
return isAuthTokens(parsed) ? parsed : null;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/storage/InMemoryTokenStorage.ts
|
|
263
|
+
var InMemoryTokenStorage = class {
|
|
264
|
+
constructor() {
|
|
265
|
+
this.tokens = null;
|
|
266
|
+
}
|
|
267
|
+
read() {
|
|
268
|
+
return Promise.resolve(this.tokens);
|
|
269
|
+
}
|
|
270
|
+
write(tokens) {
|
|
271
|
+
this.tokens = tokens;
|
|
272
|
+
return Promise.resolve();
|
|
273
|
+
}
|
|
274
|
+
clear() {
|
|
275
|
+
this.tokens = null;
|
|
276
|
+
return Promise.resolve();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/utils/normalizeKeycloakUser.ts
|
|
281
|
+
function isNonEmptyString(value) {
|
|
282
|
+
return typeof value === "string" && value !== "";
|
|
283
|
+
}
|
|
284
|
+
function firstNonEmptyString(...values) {
|
|
285
|
+
for (const v of values) {
|
|
286
|
+
if (isNonEmptyString(v)) {
|
|
287
|
+
return v;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return void 0;
|
|
291
|
+
}
|
|
292
|
+
function collectRoles(u) {
|
|
293
|
+
const roles = [];
|
|
294
|
+
const realmAccessRoles = u.realm_access?.roles;
|
|
295
|
+
if (Array.isArray(realmAccessRoles)) {
|
|
296
|
+
roles.push(...realmAccessRoles);
|
|
297
|
+
}
|
|
298
|
+
const resourceAccess = u.resource_access;
|
|
299
|
+
if (resourceAccess !== void 0) {
|
|
300
|
+
for (const v of Object.values(resourceAccess)) {
|
|
301
|
+
const resourceRoles = v.roles;
|
|
302
|
+
if (Array.isArray(resourceRoles)) {
|
|
303
|
+
roles.push(...resourceRoles);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return roles;
|
|
308
|
+
}
|
|
309
|
+
function normalizeKeycloakUser(u) {
|
|
310
|
+
if (!u) {
|
|
311
|
+
return { roles: [] };
|
|
312
|
+
}
|
|
313
|
+
const roles = collectRoles(u);
|
|
314
|
+
const fullName = [u.given_name, u.family_name].filter(isNonEmptyString).join(" ");
|
|
315
|
+
const username = firstNonEmptyString(u.preferred_username, u.name, u.email, u.sub);
|
|
316
|
+
const displayName = firstNonEmptyString(u.name, fullName, u.preferred_username, u.email, u.sub);
|
|
317
|
+
return {
|
|
318
|
+
id: u.sub,
|
|
319
|
+
username,
|
|
320
|
+
email: u.email,
|
|
321
|
+
displayName,
|
|
322
|
+
firstName: u.given_name,
|
|
323
|
+
lastName: u.family_name,
|
|
324
|
+
emailVerified: Boolean(u.email_verified),
|
|
325
|
+
roles: Array.from(new Set(roles)),
|
|
326
|
+
raw: u
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/utils/buildTokenRequestBody.ts
|
|
331
|
+
function buildAuthorizationCodeBody(input) {
|
|
332
|
+
return new URLSearchParams({
|
|
333
|
+
client_id: input.clientId,
|
|
334
|
+
grant_type: "authorization_code",
|
|
335
|
+
code: input.code,
|
|
336
|
+
redirect_uri: input.redirectUri,
|
|
337
|
+
code_verifier: input.codeVerifier
|
|
338
|
+
}).toString();
|
|
339
|
+
}
|
|
340
|
+
function buildRefreshTokenBody(input) {
|
|
341
|
+
return new URLSearchParams({
|
|
342
|
+
client_id: input.clientId,
|
|
343
|
+
grant_type: "refresh_token",
|
|
344
|
+
refresh_token: input.refreshToken
|
|
345
|
+
}).toString();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/utils/extractAuthCode.ts
|
|
349
|
+
function extractAuthCode(response) {
|
|
350
|
+
if (!response) {
|
|
351
|
+
return void 0;
|
|
352
|
+
}
|
|
353
|
+
if (response.type !== "success") {
|
|
354
|
+
return void 0;
|
|
355
|
+
}
|
|
356
|
+
const code = response.params?.code;
|
|
357
|
+
if (typeof code !== "string" || code === "") {
|
|
358
|
+
return void 0;
|
|
359
|
+
}
|
|
360
|
+
return code;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/utils/decodeJwt.ts
|
|
364
|
+
function decodeJwt(token) {
|
|
365
|
+
if (typeof token !== "string" || token === "") {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const parts = token.split(".");
|
|
369
|
+
if (parts.length !== 3) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const payload = parts[1];
|
|
373
|
+
if (payload === void 0 || payload === "") {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
const json = base64UrlDecode(payload);
|
|
378
|
+
const parsed = JSON.parse(json);
|
|
379
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
return parsed;
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
var BASE64_PAD_LENGTH = 4;
|
|
388
|
+
function base64UrlDecode(input) {
|
|
389
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
390
|
+
const padLength = (BASE64_PAD_LENGTH - normalized.length % BASE64_PAD_LENGTH) % BASE64_PAD_LENGTH;
|
|
391
|
+
const padded = normalized + "=".repeat(padLength);
|
|
392
|
+
if (typeof globalThis.atob !== "function") {
|
|
393
|
+
throw new Error("decodeJwt: globalThis.atob is unavailable in this runtime");
|
|
394
|
+
}
|
|
395
|
+
return decodeUtf8(globalThis.atob(padded));
|
|
396
|
+
}
|
|
397
|
+
function decodeUtf8(binary) {
|
|
398
|
+
if (typeof TextDecoder === "undefined") {
|
|
399
|
+
return binary;
|
|
400
|
+
}
|
|
401
|
+
const bytes = new Uint8Array(binary.length);
|
|
402
|
+
for (let i = 0; i < binary.length; i++) {
|
|
403
|
+
bytes[i] = binary.charCodeAt(i);
|
|
404
|
+
}
|
|
405
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/utils/normalizeTokenResponse.ts
|
|
409
|
+
function asString(value) {
|
|
410
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
411
|
+
}
|
|
412
|
+
function asNumber(value) {
|
|
413
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
414
|
+
}
|
|
415
|
+
function normalizeTokenResponse(raw) {
|
|
416
|
+
const accessToken = asString(raw.access_token);
|
|
417
|
+
if (accessToken === void 0) {
|
|
418
|
+
throw new Error("Token response missing access_token");
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
accessToken,
|
|
422
|
+
refreshToken: asString(raw.refresh_token),
|
|
423
|
+
idToken: asString(raw.id_token),
|
|
424
|
+
expiresIn: asNumber(raw.expires_in),
|
|
425
|
+
tokenType: asString(raw.token_type),
|
|
426
|
+
scope: asString(raw.scope)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function tokenResponseToAuthTokens(response, now = Date.now()) {
|
|
430
|
+
return {
|
|
431
|
+
accessToken: response.accessToken,
|
|
432
|
+
refreshToken: response.refreshToken,
|
|
433
|
+
idToken: response.idToken,
|
|
434
|
+
expiresAt: computeExpiresAt(response.expiresIn, now)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export { AuthClient, BrowserStorageTokenStorage, InMemoryTokenStorage, KeycloakRoles, buildAuthorizationCodeBody, buildAuthorizationEndpoint, buildAuthorizationUrl, buildIssuerUrl, buildLogoutEndpoint, buildRefreshTokenBody, buildTokenEndpoint, buildUserInfoEndpoint, computeExpiresAt, decodeJwt, extractAuthCode, isKeycloakRole, isTokenExpired, normalizeKeycloakUser, normalizeTokenResponse, parseBaseUrlFromIssuer, parseRealmFromIssuer, tokenResponseToAuthTokens };
|
|
439
|
+
//# sourceMappingURL=index.mjs.map
|
|
440
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/buildKeycloakUrls.ts","../src/utils/isTokenExpired.ts","../src/utils/parseRealmFromIssuer.ts","../src/AuthClient.ts","../src/types/KeycloakRoles.ts","../src/storage/BrowserStorageTokenStorage.ts","../src/storage/InMemoryTokenStorage.ts","../src/utils/normalizeKeycloakUser.ts","../src/utils/buildTokenRequestBody.ts","../src/utils/extractAuthCode.ts","../src/utils/decodeJwt.ts","../src/utils/normalizeTokenResponse.ts"],"names":["KeycloakRoles"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * URL builders for the realm-aware Keycloak surface area.\n *\n * Every helper takes `baseUrl` and `realm` explicitly — no hardcoded realm\n * names. This is the contract that Phase 2 of the product split relies on:\n * the same package serves the future Questioner-realm app and OnlineMenu-realm\n * app without code change.\n */\n\nconst REALM_PATH_PREFIX = '/realms';\nconst PROTOCOL_PATH = '/protocol/openid-connect';\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/$/, '');\n}\n\n/**\n * Compute the issuer URL: `{baseUrl}/realms/{realm}`.\n */\nexport function buildIssuerUrl(baseUrl: string, realm: string): string {\n return `${trimTrailingSlash(baseUrl)}${REALM_PATH_PREFIX}/${encodeURIComponent(realm)}`;\n}\n\n/**\n * Compute the authorization endpoint URL.\n */\nexport function buildAuthorizationEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/auth`;\n}\n\n/**\n * Compute the token endpoint URL.\n */\nexport function buildTokenEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/token`;\n}\n\n/**\n * Compute the userinfo endpoint URL.\n */\nexport function buildUserInfoEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/userinfo`;\n}\n\n/**\n * Compute the logout endpoint URL.\n */\nexport function buildLogoutEndpoint(baseUrl: string, realm: string): string {\n return `${buildIssuerUrl(baseUrl, realm)}${PROTOCOL_PATH}/logout`;\n}\n\nexport interface AuthorizationUrlInput {\n baseUrl: string;\n realm: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n}\n\n/**\n * Build a complete authorization URL the user agent can navigate to.\n *\n * All PKCE-related fields are optional so this helper also serves\n * non-PKCE flows (e.g. confidential server-side clients) — but PKCE is\n * the recommended path for SPA / native consumers.\n */\nexport function buildAuthorizationUrl(input: AuthorizationUrlInput): string {\n const params = new URLSearchParams({\n client_id: input.clientId,\n redirect_uri: input.redirectUri,\n response_type: 'code',\n });\n if (typeof input.scope === 'string' && input.scope !== '') {\n params.set('scope', input.scope);\n }\n if (typeof input.state === 'string' && input.state !== '') {\n params.set('state', input.state);\n }\n if (typeof input.codeChallenge === 'string' && input.codeChallenge !== '') {\n params.set('code_challenge', input.codeChallenge);\n params.set('code_challenge_method', input.codeChallengeMethod ?? 'S256');\n }\n return `${buildAuthorizationEndpoint(input.baseUrl, input.realm)}?${params.toString()}`;\n}\n","import type { AuthTokens } from '../types/AuthTokens';\n\nconst SECONDS_TO_MILLIS = 1000;\nconst DEFAULT_LEEWAY_MS = 30 * SECONDS_TO_MILLIS;\n\n/**\n * Determine whether a token bundle is expired.\n *\n * `expiresAt` is interpreted as an absolute UNIX millisecond timestamp.\n *\n * `leewayMs` (default 30 s) shaves a small window off the expiry to compensate\n * for clock skew and round-trip latency: a token that expires at exactly\n * `Date.now()` is essentially useless because by the time the request arrives\n * at the API it will have expired.\n */\nexport function isTokenExpired(\n tokens: Pick<AuthTokens, 'expiresAt'> | null | undefined,\n leewayMs: number = DEFAULT_LEEWAY_MS,\n now: number = Date.now(),\n): boolean {\n if (!tokens) {\n return true;\n }\n if (typeof tokens.expiresAt !== 'number' || tokens.expiresAt <= 0) {\n return true;\n }\n return tokens.expiresAt - leewayMs <= now;\n}\n\n/**\n * Compute the absolute expiry timestamp from a token endpoint `expires_in` value.\n *\n * Returns `0` when `expiresIn` is missing or non-positive — signalling \"unknown\n * expiry, treat as expired\" downstream.\n */\nexport function computeExpiresAt(\n expiresInSeconds: number | undefined,\n now: number = Date.now(),\n): number {\n if (typeof expiresInSeconds !== 'number' || expiresInSeconds <= 0) {\n return 0;\n }\n return now + expiresInSeconds * SECONDS_TO_MILLIS;\n}\n","/**\n * Extract the realm name from a Keycloak issuer URL.\n *\n * Keycloak issuer URLs follow the shape `{baseUrl}/realms/{realm}` (with optional\n * `/protocol/openid-connect` suffix on token endpoints). This helper reverses\n * that convention so existing apps that store only the issuer URL can derive\n * `realm` for the realm-aware {@link AuthClient} constructor.\n *\n * @returns the realm name, or `null` if the URL doesn't match the convention.\n */\nexport function parseRealmFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const match = /\\/realms\\/([^/?#]+)/i.exec(issuerUrl);\n if (!match || match[1] === undefined || match[1] === '') {\n return null;\n }\n return decodeURIComponent(match[1]);\n}\n\n/**\n * Extract the base URL (scheme + host + optional path prefix) from a\n * Keycloak issuer URL by stripping the `/realms/{realm}...` suffix.\n *\n * Returns the original input unchanged when no `/realms/` segment is found —\n * callers that need strict validation should pair this with `parseRealmFromIssuer`.\n */\nexport function parseBaseUrlFromIssuer(issuerUrl: string | null | undefined): string | null {\n if (typeof issuerUrl !== 'string' || issuerUrl === '') {\n return null;\n }\n const idx = issuerUrl.search(/\\/realms\\//i);\n if (idx === -1) {\n return issuerUrl.replace(/\\/$/, '');\n }\n return issuerUrl.substring(0, idx).replace(/\\/$/, '');\n}\n","import {\n buildAuthorizationEndpoint,\n buildAuthorizationUrl,\n buildIssuerUrl,\n buildLogoutEndpoint,\n buildTokenEndpoint,\n buildUserInfoEndpoint,\n} from './utils/buildKeycloakUrls';\nimport { isTokenExpired } from './utils/isTokenExpired';\nimport { parseBaseUrlFromIssuer, parseRealmFromIssuer } from './utils/parseRealmFromIssuer';\n\nimport type { AuthClientConfig } from './types/AuthClientConfig';\nimport type { AuthTokens } from './types/AuthTokens';\nimport type { TokenStorage } from './types/TokenStorage';\n\nconst DEFAULT_SCOPE = 'openid profile email';\n\n/**\n * Inputs to {@link AuthClient.fromIssuerUrl}.\n *\n * Used by consumers that store only an issuer URL and want to derive `realm`\n * + `baseUrl` rather than configure them separately.\n */\nexport interface AuthClientFromIssuerInput {\n issuerUrl: string;\n clientId: string;\n redirectUri?: string;\n scope?: string;\n}\n\nexport class AuthClient {\n private readonly config: AuthClientConfig;\n private readonly tokenStorage: TokenStorage;\n\n /**\n * @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.\n */\n constructor(config: AuthClientConfig, storage: TokenStorage) {\n AuthClient.validateConfig(config);\n this.config = {\n ...config,\n scope: config.scope ?? DEFAULT_SCOPE,\n };\n this.tokenStorage = storage;\n }\n\n /**\n * Build an {@link AuthClient} from a standalone issuer URL by parsing the\n * realm and base URL. Useful when migrating from the legacy\n * `KEYCLOAK_ISSUER` env var convention.\n *\n * @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.\n */\n static fromIssuerUrl(input: AuthClientFromIssuerInput, storage: TokenStorage): AuthClient {\n const realm = parseRealmFromIssuer(input.issuerUrl);\n const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);\n if (realm === null || baseUrl === null || baseUrl === '') {\n throw new Error(`AuthClient.fromIssuerUrl: cannot parse realm from \"${input.issuerUrl}\"`);\n }\n return new AuthClient(\n {\n baseUrl,\n realm,\n clientId: input.clientId,\n redirectUri: input.redirectUri,\n scope: input.scope,\n },\n storage,\n );\n }\n\n private static validateConfig(config: AuthClientConfig): void {\n if (typeof config.baseUrl !== 'string' || config.baseUrl === '') {\n throw new Error('AuthClient: baseUrl is required');\n }\n if (typeof config.realm !== 'string' || config.realm === '') {\n throw new Error('AuthClient: realm is required');\n }\n if (typeof config.clientId !== 'string' || config.clientId === '') {\n throw new Error('AuthClient: clientId is required');\n }\n }\n\n get realm(): string {\n return this.config.realm;\n }\n\n get clientId(): string {\n return this.config.clientId;\n }\n\n get baseUrl(): string {\n return this.config.baseUrl.replace(/\\/$/, '');\n }\n\n get scope(): string {\n // Constructor always materialises a scope (either user-supplied or DEFAULT_SCOPE).\n return this.config.scope as string;\n }\n\n get redirectUri(): string | undefined {\n return this.config.redirectUri;\n }\n\n /** Issuer URL: `{baseUrl}/realms/{realm}`. */\n get issuerUrl(): string {\n return buildIssuerUrl(this.baseUrl, this.realm);\n }\n\n get authorizationEndpoint(): string {\n return buildAuthorizationEndpoint(this.baseUrl, this.realm);\n }\n\n get tokenEndpoint(): string {\n return buildTokenEndpoint(this.baseUrl, this.realm);\n }\n\n get userInfoEndpoint(): string {\n return buildUserInfoEndpoint(this.baseUrl, this.realm);\n }\n\n get logoutEndpoint(): string {\n return buildLogoutEndpoint(this.baseUrl, this.realm);\n }\n\n /**\n * Build a fully-formed authorization URL the user agent can navigate to.\n *\n * @throws Error when `redirectUri` is not configured.\n */\n buildAuthorizationUrl(input: {\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n } = {}): string {\n if (typeof this.config.redirectUri !== 'string' || this.config.redirectUri === '') {\n throw new Error('AuthClient.buildAuthorizationUrl: redirectUri is required');\n }\n return buildAuthorizationUrl({\n baseUrl: this.baseUrl,\n realm: this.realm,\n clientId: this.clientId,\n redirectUri: this.config.redirectUri,\n scope: this.scope,\n state: input.state,\n codeChallenge: input.codeChallenge,\n codeChallengeMethod: input.codeChallengeMethod,\n });\n }\n\n async getTokens(): Promise<AuthTokens | null> {\n return this.tokenStorage.read();\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n return this.tokenStorage.write(tokens);\n }\n\n async clearTokens(): Promise<void> {\n return this.tokenStorage.clear();\n }\n\n /**\n * Read the current access token if it exists and is not expired.\n * Returns `null` for \"no usable token\".\n */\n async getAccessToken(now: number = Date.now()): Promise<string | null> {\n const tokens = await this.tokenStorage.read();\n if (tokens === null) {\n return null;\n }\n if (isTokenExpired(tokens, undefined, now)) {\n return null;\n }\n return tokens.accessToken;\n }\n}\n","/**\n * Roles emitted by Keycloak realms in the dloizides.com portfolio.\n *\n * Lives in its own file per the project convention: each exported `const enum`\n * sits alone so it can be imported without dragging the rest of the type tree.\n */\nexport const enum KeycloakRoles {\n SuperUser = 'superUser',\n Admin = 'admin',\n User = 'user',\n}\n\nconst KEYCLOAK_ROLE_VALUES: readonly string[] = [\n KeycloakRoles.SuperUser,\n KeycloakRoles.Admin,\n KeycloakRoles.User,\n];\n\n/**\n * Type guard that narrows a string to a known {@link KeycloakRoles} value.\n *\n * Use this when ingesting role claims from the network, where the wire payload\n * is `string[]` but downstream code wants `KeycloakRoles[]`.\n */\nexport function isKeycloakRole(value: string): value is KeycloakRoles {\n return KEYCLOAK_ROLE_VALUES.includes(value);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * Subset of `Storage` we actually use. Lets callers inject `localStorage`,\n * `sessionStorage`, or any compatible polyfill.\n */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface BrowserStorageTokenStorageOptions {\n storage: StorageLike;\n /** Storage key. Defaults to `auth.tokens`. */\n key?: string;\n}\n\nconst DEFAULT_KEY = 'auth.tokens';\n\nfunction isAuthTokens(value: unknown): value is AuthTokens {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.accessToken === 'string' && typeof candidate.expiresAt === 'number';\n}\n\n/**\n * Persist tokens in any `Storage`-shaped backend (`localStorage`, `sessionStorage`,\n * AsyncStorage shim, etc.). The class is sync-aware but exposes a Promise-based\n * API to match {@link TokenStorage}.\n *\n * Errors during read are swallowed and surfaced as `null` (corrupt JSON, denied\n * access in some private-mode browsers, etc.). Errors during write/clear are\n * propagated so callers can decide whether to retry or fall back.\n */\nexport class BrowserStorageTokenStorage implements TokenStorage {\n private readonly storage: StorageLike;\n private readonly key: string;\n\n constructor(options: BrowserStorageTokenStorageOptions) {\n this.storage = options.storage;\n this.key = options.key ?? DEFAULT_KEY;\n }\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.readSync());\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.storage.setItem(this.key, JSON.stringify(tokens));\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.storage.removeItem(this.key);\n return Promise.resolve();\n }\n\n private readSync(): AuthTokens | null {\n try {\n const raw = this.storage.getItem(this.key);\n if (raw === null || raw === '') {\n return null;\n }\n const parsed: unknown = JSON.parse(raw);\n return isAuthTokens(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { TokenStorage } from '../types/TokenStorage';\n\n/**\n * In-memory storage backed by a single instance variable.\n *\n * Useful for tests, server-side rendering, and as a default fallback when no\n * platform-specific storage is available. Tokens are lost on process exit.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n read(): Promise<AuthTokens | null> {\n return Promise.resolve(this.tokens);\n }\n\n write(tokens: AuthTokens): Promise<void> {\n this.tokens = tokens;\n return Promise.resolve();\n }\n\n clear(): Promise<void> {\n this.tokens = null;\n return Promise.resolve();\n }\n}\n","import { KeycloakRoles } from '../types/KeycloakRoles';\n\nimport type { KeycloakUserInfo } from '../types/KeycloakUserInfo';\nimport type { NormalizedUser } from '../types/NormalizedUser';\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value !== '';\n}\n\nfunction firstNonEmptyString(...values: unknown[]): string | undefined {\n for (const v of values) {\n if (isNonEmptyString(v)) {\n return v;\n }\n }\n return undefined;\n}\n\nfunction collectRoles(u: KeycloakUserInfo): KeycloakRoles[] {\n const roles: KeycloakRoles[] = [];\n const realmAccessRoles = u.realm_access?.roles;\n if (Array.isArray(realmAccessRoles)) {\n roles.push(...realmAccessRoles);\n }\n\n const resourceAccess = u.resource_access;\n if (resourceAccess !== undefined) {\n for (const v of Object.values(resourceAccess)) {\n const resourceRoles = v.roles;\n if (Array.isArray(resourceRoles)) {\n roles.push(...resourceRoles);\n }\n }\n }\n return roles;\n}\n\n/**\n * Convert a Keycloak `/userinfo` payload into a flat, app-friendly user object.\n *\n * - Aggregates `realm_access.roles` and every `resource_access[*].roles` into a\n * deduplicated `roles` array.\n * - Picks a sensible `displayName` / `username` from whatever claims are\n * present (Keycloak realms vary in which fields they emit).\n * - Returns a safe default (`{ roles: [] }`) when input is undefined.\n */\nexport function normalizeKeycloakUser(u?: KeycloakUserInfo): NormalizedUser {\n if (!u) {\n return { roles: [] };\n }\n const roles = collectRoles(u);\n const fullName = [u.given_name, u.family_name].filter(isNonEmptyString).join(' ');\n const username = firstNonEmptyString(u.preferred_username, u.name, u.email, u.sub);\n const displayName = firstNonEmptyString(u.name, fullName, u.preferred_username, u.email, u.sub);\n\n return {\n id: u.sub,\n username,\n email: u.email,\n displayName,\n firstName: u.given_name,\n lastName: u.family_name,\n emailVerified: Boolean(u.email_verified),\n roles: Array.from(new Set(roles)),\n raw: u,\n };\n}\n","/**\n * Inputs for the OAuth `authorization_code` token request.\n */\nexport interface AuthorizationCodeBodyInput {\n clientId: string;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}\n\n/**\n * Inputs for the OAuth `refresh_token` token request.\n */\nexport interface RefreshTokenBodyInput {\n clientId: string;\n refreshToken: string;\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=authorization_code` token endpoint call (PKCE flow).\n */\nexport function buildAuthorizationCodeBody(input: AuthorizationCodeBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'authorization_code',\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n }).toString();\n}\n\n/**\n * Build the `application/x-www-form-urlencoded` body for the\n * `grant_type=refresh_token` token endpoint call.\n */\nexport function buildRefreshTokenBody(input: RefreshTokenBodyInput): string {\n return new URLSearchParams({\n client_id: input.clientId,\n grant_type: 'refresh_token',\n refresh_token: input.refreshToken,\n }).toString();\n}\n","/**\n * Loose shape of an `expo-auth-session` (or browser-side) authorization response.\n *\n * Kept as a structural type rather than importing from `expo-auth-session` so\n * the package stays usable in plain web apps and Node tests.\n */\nexport interface AuthorizationResponseLike {\n type?: string;\n params?: { code?: string; error?: string };\n}\n\n/**\n * Pull the authorization `code` out of a successful redirect response.\n *\n * Returns `undefined` when the response is missing, indicates an error type,\n * or doesn't carry a non-empty `code` query param.\n */\nexport function extractAuthCode(\n response: AuthorizationResponseLike | null | undefined,\n): string | undefined {\n if (!response) {\n return undefined;\n }\n if (response.type !== 'success') {\n return undefined;\n }\n const code = response.params?.code;\n if (typeof code !== 'string' || code === '') {\n return undefined;\n }\n return code;\n}\n","/**\n * Decode the payload segment of a compact JWT.\n *\n * No signature verification — that responsibility belongs to the backend that\n * accepts the token. This helper is for UI concerns: reading `exp` to schedule\n * refresh, reading custom claims for routing decisions, etc.\n *\n * Returns `null` when the input is malformed, base64url-decodes incorrectly, or\n * does not produce a JSON object payload.\n *\n * Runtime requirement: a global `atob` function. Available in browsers, in\n * Node ≥ 16, and in modern bundler test envs (jsdom, node-jest).\n */\nexport function decodeJwt<T = Record<string, unknown>>(token: string | null | undefined): T | null {\n if (typeof token !== 'string' || token === '') {\n return null;\n }\n const parts = token.split('.');\n if (parts.length !== 3) {\n return null;\n }\n const payload = parts[1];\n if (payload === undefined || payload === '') {\n return null;\n }\n try {\n const json = base64UrlDecode(payload);\n const parsed: unknown = JSON.parse(json);\n if (typeof parsed !== 'object' || parsed === null) {\n return null;\n }\n return parsed as T;\n } catch {\n return null;\n }\n}\n\nconst BASE64_PAD_LENGTH = 4;\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, '+').replace(/_/g, '/');\n const padLength =\n (BASE64_PAD_LENGTH - (normalized.length % BASE64_PAD_LENGTH)) % BASE64_PAD_LENGTH;\n const padded = normalized + '='.repeat(padLength);\n if (typeof globalThis.atob !== 'function') {\n throw new Error('decodeJwt: globalThis.atob is unavailable in this runtime');\n }\n return decodeUtf8(globalThis.atob(padded));\n}\n\nfunction decodeUtf8(binary: string): string {\n // `atob` returns a \"binary string\" where each char code is one byte. Convert\n // to UTF-8 properly using TextDecoder when available (browsers + Node).\n if (typeof TextDecoder === 'undefined') {\n return binary;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder('utf-8').decode(bytes);\n}\n","import type { AuthTokens } from '../types/AuthTokens';\nimport type { RawTokenResponse, TokenResponse } from '../types/TokenResponse';\nimport { computeExpiresAt } from './isTokenExpired';\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Map a raw OIDC token endpoint response (snake_case) to camelCase.\n *\n * Throws when `access_token` is missing or empty — callers should let this\n * propagate to the auth state machine, which treats it as a login failure.\n */\nexport function normalizeTokenResponse(raw: RawTokenResponse): TokenResponse {\n const accessToken = asString(raw.access_token);\n if (accessToken === undefined) {\n throw new Error('Token response missing access_token');\n }\n return {\n accessToken,\n refreshToken: asString(raw.refresh_token),\n idToken: asString(raw.id_token),\n expiresIn: asNumber(raw.expires_in),\n tokenType: asString(raw.token_type),\n scope: asString(raw.scope),\n };\n}\n\n/**\n * Convert a normalized {@link TokenResponse} into a persistable\n * {@link AuthTokens} bundle by computing `expiresAt` from `expiresIn`.\n */\nexport function tokenResponseToAuthTokens(\n response: TokenResponse,\n now: number = Date.now(),\n): AuthTokens {\n return {\n accessToken: response.accessToken,\n refreshToken: response.refreshToken,\n idToken: response.idToken,\n expiresAt: computeExpiresAt(response.expiresIn, now),\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dloizides/auth-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Realm-aware Keycloak/OIDC client for the dloizides.com portfolio. PKCE flow, token refresh, storage abstraction. Takes realm and clientId as config — no hardcoding.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"keycloak",
|
|
7
|
+
"oidc",
|
|
8
|
+
"pkce",
|
|
9
|
+
"auth",
|
|
10
|
+
"dloizides"
|
|
11
|
+
],
|
|
12
|
+
"author": "dloizides",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/openmindednewby/auth-client.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/openmindednewby/auth-client#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/openmindednewby/auth-client/issues"
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.mts",
|
|
33
|
+
"default": "./dist/index.mjs"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"sideEffects": false,
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "rimraf dist && tsup",
|
|
48
|
+
"build:watch": "tsup --watch",
|
|
49
|
+
"test": "jest",
|
|
50
|
+
"test:watch": "jest --watch",
|
|
51
|
+
"test:coverage": "jest --coverage",
|
|
52
|
+
"lint": "eslint src --ext .ts",
|
|
53
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"clean": "rimraf dist",
|
|
56
|
+
"security:audit": "npm audit --audit-level=high",
|
|
57
|
+
"deps:outdated": "npm outdated || exit 0",
|
|
58
|
+
"deps:unused": "npx depcheck --ignores \"@types/jest,@types/node,rimraf\"",
|
|
59
|
+
"deps:licenses": "npx license-checker --onlyAllow \"MIT;Apache-2.0;ISC;BSD-2-Clause;BSD-3-Clause;0BSD;Unlicense;CC0-1.0\"",
|
|
60
|
+
"deps:health": "npm run deps:outdated && npm run deps:unused",
|
|
61
|
+
"prepublishOnly": "npm run clean && npm run build && npm run test"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/jest": "^29.5.0",
|
|
65
|
+
"@types/node": "^20.19.32",
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
67
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
68
|
+
"eslint": "^8.57.0",
|
|
69
|
+
"eslint-plugin-sonarjs": "^4.0.3",
|
|
70
|
+
"jest": "^29.7.0",
|
|
71
|
+
"rimraf": "^5.0.0",
|
|
72
|
+
"ts-jest": "^29.1.0",
|
|
73
|
+
"tsup": "^8.0.0",
|
|
74
|
+
"typescript": "^5.4.0"
|
|
75
|
+
}
|
|
76
|
+
}
|