@civic/auth 0.0.1-beta.4 → 0.0.1-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/{chunk-NQPMNXBL.mjs → chunk-EAANLFR5.mjs} +4 -10
- package/dist/chunk-EAANLFR5.mjs.map +1 -0
- package/dist/{chunk-3UIVD6NR.mjs → chunk-EGFTMH5S.mjs} +11 -65
- package/dist/chunk-EGFTMH5S.mjs.map +1 -0
- package/dist/{chunk-T47HULF6.js → chunk-KCSGIIPA.js} +21 -75
- package/dist/chunk-KCSGIIPA.js.map +1 -0
- package/dist/{chunk-WPISYQG3.js → chunk-MVO4UZ2A.js} +5 -11
- package/dist/chunk-MVO4UZ2A.js.map +1 -0
- package/dist/chunk-PMDIR5XE.mjs +502 -0
- package/dist/chunk-PMDIR5XE.mjs.map +1 -0
- package/dist/chunk-YNLXRD5L.js +502 -0
- package/dist/chunk-YNLXRD5L.js.map +1 -0
- package/dist/{index-DoDoIY_K.d.mts → index-Bfi0hVMZ.d.mts} +5 -26
- package/dist/{index-DoDoIY_K.d.ts → index-Bfi0hVMZ.d.ts} +5 -26
- package/dist/index.d.mts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/nextjs.d.mts +2 -3
- package/dist/nextjs.d.ts +2 -3
- package/dist/nextjs.js +21 -35
- package/dist/nextjs.js.map +1 -1
- package/dist/nextjs.mjs +7 -21
- package/dist/nextjs.mjs.map +1 -1
- package/dist/react.d.mts +29 -47
- package/dist/react.d.ts +29 -47
- package/dist/react.js +278 -487
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +300 -509
- package/dist/react.mjs.map +1 -1
- package/dist/server.d.mts +2 -7
- package/dist/server.d.ts +2 -7
- package/dist/server.js +4 -3
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +4 -3
- package/package.json +1 -1
- package/dist/chunk-3UIVD6NR.mjs.map +0 -1
- package/dist/chunk-KBDRDCE5.mjs +0 -239
- package/dist/chunk-KBDRDCE5.mjs.map +0 -1
- package/dist/chunk-NQPMNXBL.mjs.map +0 -1
- package/dist/chunk-OLT5HB3G.js +0 -239
- package/dist/chunk-OLT5HB3G.js.map +0 -1
- package/dist/chunk-T47HULF6.js.map +0 -1
- package/dist/chunk-WPISYQG3.js.map +0 -1
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var _chunkCRTRMMJ7js = require('./chunk-CRTRMMJ7.js');
|
|
6
|
+
|
|
7
|
+
// src/shared/types.ts
|
|
8
|
+
var OAuthTokens = /* @__PURE__ */ ((OAuthTokens2) => {
|
|
9
|
+
OAuthTokens2["ID_TOKEN"] = "id_token";
|
|
10
|
+
OAuthTokens2["ACCESS_TOKEN"] = "access_token";
|
|
11
|
+
OAuthTokens2["REFRESH_TOKEN"] = "refresh_token";
|
|
12
|
+
return OAuthTokens2;
|
|
13
|
+
})(OAuthTokens || {});
|
|
14
|
+
|
|
15
|
+
// src/shared/util.ts
|
|
16
|
+
var _oauth2 = require('oslo/oauth2');
|
|
17
|
+
|
|
18
|
+
// src/lib/oauth.ts
|
|
19
|
+
var _uuid = require('uuid');
|
|
20
|
+
var getIssuerVariations = (issuer) => {
|
|
21
|
+
const issuerWithoutSlash = issuer.endsWith("/") ? issuer.slice(0, issuer.length - 1) : issuer;
|
|
22
|
+
const issuerWithSlash = `${issuerWithoutSlash}/`;
|
|
23
|
+
return [issuerWithoutSlash, issuerWithSlash];
|
|
24
|
+
};
|
|
25
|
+
var addSlashIfNeeded = (url) => url.endsWith("/") ? url : `${url}/`;
|
|
26
|
+
var getOauthEndpoints = (oauthServer) => _chunkCRTRMMJ7js.__async.call(void 0, void 0, null, function* () {
|
|
27
|
+
const openIdConfigResponse = yield fetch(
|
|
28
|
+
`${addSlashIfNeeded(oauthServer)}.well-known/openid-configuration`
|
|
29
|
+
);
|
|
30
|
+
const openIdConfig = yield openIdConfigResponse.json();
|
|
31
|
+
return {
|
|
32
|
+
jwks: openIdConfig.jwks_uri,
|
|
33
|
+
auth: openIdConfig.authorization_endpoint,
|
|
34
|
+
token: openIdConfig.token_endpoint,
|
|
35
|
+
userinfo: openIdConfig.userinfo_endpoint
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
var generateState = (displayMode) => {
|
|
39
|
+
const jsonString = JSON.stringify({
|
|
40
|
+
uuid: _uuid.v4.call(void 0, ),
|
|
41
|
+
displayMode
|
|
42
|
+
});
|
|
43
|
+
return btoa(jsonString);
|
|
44
|
+
};
|
|
45
|
+
var displayModeFromState = (state, sessionDisplayMode) => {
|
|
46
|
+
try {
|
|
47
|
+
const jsonString = atob(state);
|
|
48
|
+
return JSON.parse(jsonString).displayMode;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error("Failed to parse displayMode from state:", state);
|
|
51
|
+
return sessionDisplayMode;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/shared/util.ts
|
|
56
|
+
var _jose = require('jose'); var jose = _interopRequireWildcard(_jose);
|
|
57
|
+
|
|
58
|
+
// src/utils.ts
|
|
59
|
+
var _clsx = require('clsx');
|
|
60
|
+
var _tailwindmerge = require('tailwind-merge');
|
|
61
|
+
var cn = (...inputs) => {
|
|
62
|
+
return _tailwindmerge.twMerge.call(void 0, _clsx.clsx.call(void 0, inputs));
|
|
63
|
+
};
|
|
64
|
+
var withoutUndefined = (obj) => {
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const key in obj) {
|
|
67
|
+
if (obj[key] !== void 0) {
|
|
68
|
+
result[key] = obj[key];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/shared/UserSession.ts
|
|
75
|
+
var GenericUserSession = class {
|
|
76
|
+
constructor(storage) {
|
|
77
|
+
this.storage = storage;
|
|
78
|
+
}
|
|
79
|
+
get() {
|
|
80
|
+
const user = this.storage.get("user" /* USER */);
|
|
81
|
+
return user ? JSON.parse(user) : null;
|
|
82
|
+
}
|
|
83
|
+
set(user) {
|
|
84
|
+
const value = user ? JSON.stringify(user) : "";
|
|
85
|
+
this.storage.set("user" /* USER */, value);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/shared/util.ts
|
|
90
|
+
function deriveCodeChallenge(codeVerifier, method = "S256") {
|
|
91
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
92
|
+
if (method === "Plain") {
|
|
93
|
+
console.warn("Using insecure plain code challenge method");
|
|
94
|
+
return codeVerifier;
|
|
95
|
+
}
|
|
96
|
+
const encoder = new TextEncoder();
|
|
97
|
+
const data = encoder.encode(codeVerifier);
|
|
98
|
+
const digest = yield crypto.subtle.digest("SHA-256", data);
|
|
99
|
+
return btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function getEndpointsWithOverrides(_0) {
|
|
103
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, arguments, function* (oauthServer, endpointOverrides = {}) {
|
|
104
|
+
const endpoints = yield getOauthEndpoints(oauthServer);
|
|
105
|
+
return _chunkCRTRMMJ7js.__spreadValues.call(void 0, _chunkCRTRMMJ7js.__spreadValues.call(void 0, {}, endpoints), endpointOverrides);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function generateOauthLoginUrl(config) {
|
|
109
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
110
|
+
const endpoints = yield getEndpointsWithOverrides(
|
|
111
|
+
config.oauthServer,
|
|
112
|
+
config.endpointOverrides
|
|
113
|
+
);
|
|
114
|
+
const oauth2Client = buildOauth2Client(
|
|
115
|
+
config.clientId,
|
|
116
|
+
config.redirectUrl,
|
|
117
|
+
endpoints
|
|
118
|
+
);
|
|
119
|
+
const challenge = yield config.pkceConsumer.getCodeChallenge();
|
|
120
|
+
const oAuthUrl = yield oauth2Client.createAuthorizationURL({
|
|
121
|
+
state: config.state,
|
|
122
|
+
scopes: config.scopes
|
|
123
|
+
});
|
|
124
|
+
oAuthUrl.searchParams.append("code_challenge", challenge);
|
|
125
|
+
oAuthUrl.searchParams.append("code_challenge_method", "S256");
|
|
126
|
+
if (config.nonce) {
|
|
127
|
+
oAuthUrl.searchParams.append("nonce", config.nonce);
|
|
128
|
+
}
|
|
129
|
+
oAuthUrl.searchParams.append("prompt", "consent");
|
|
130
|
+
console.log("Generated OAuth URL", oAuthUrl.toString());
|
|
131
|
+
return oAuthUrl;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function generateOauthLogoutUrl(config) {
|
|
135
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
136
|
+
return new URL("http://localhost");
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function buildOauth2Client(clientId, redirectUri, endpoints) {
|
|
140
|
+
return new (0, _oauth2.OAuth2Client)(clientId, endpoints.auth, endpoints.token, {
|
|
141
|
+
redirectURI: redirectUri
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function exchangeTokens(code, state, pkceProducer, oauth2Client, oauthServer, endpoints) {
|
|
145
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
146
|
+
const codeVerifier = yield pkceProducer.getCodeVerifier();
|
|
147
|
+
if (!codeVerifier) throw new Error("Code verifier not found in state");
|
|
148
|
+
const tokens = yield oauth2Client.validateAuthorizationCode(code, {
|
|
149
|
+
codeVerifier
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
yield validateOauth2Tokens(tokens, endpoints, oauth2Client, oauthServer);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error("tokenExchange error", { error, tokens });
|
|
155
|
+
throw new Error(
|
|
156
|
+
`OIDC tokens validation failed: ${error.message}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return tokens;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function storeTokens(storage, tokens) {
|
|
163
|
+
storage.set("id_token" /* ID_TOKEN */, tokens.id_token);
|
|
164
|
+
storage.set("access_token" /* ACCESS_TOKEN */, tokens.access_token);
|
|
165
|
+
if (tokens.refresh_token)
|
|
166
|
+
storage.set("refresh_token" /* REFRESH_TOKEN */, tokens.refresh_token);
|
|
167
|
+
}
|
|
168
|
+
function clearTokens(storage) {
|
|
169
|
+
Object.values(OAuthTokens).forEach((cookie) => {
|
|
170
|
+
storage.set(cookie, "");
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function clearUser(storage) {
|
|
174
|
+
const userSession = new GenericUserSession(storage);
|
|
175
|
+
userSession.set(null);
|
|
176
|
+
}
|
|
177
|
+
function retrieveTokens(storage) {
|
|
178
|
+
const idToken = storage.get("id_token" /* ID_TOKEN */);
|
|
179
|
+
const accessToken = storage.get("access_token" /* ACCESS_TOKEN */);
|
|
180
|
+
const refreshToken = storage.get("refresh_token" /* REFRESH_TOKEN */);
|
|
181
|
+
if (!idToken || !accessToken) return null;
|
|
182
|
+
return {
|
|
183
|
+
id_token: idToken,
|
|
184
|
+
access_token: accessToken,
|
|
185
|
+
refresh_token: refreshToken != null ? refreshToken : void 0
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function validateOauth2Tokens(tokens, endpoints, oauth2Client, issuer) {
|
|
189
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
190
|
+
const JWKS = jose.createRemoteJWKSet(new URL(endpoints.jwks));
|
|
191
|
+
const idTokenResponse = yield jose.jwtVerify(
|
|
192
|
+
tokens.id_token,
|
|
193
|
+
JWKS,
|
|
194
|
+
{
|
|
195
|
+
issuer: getIssuerVariations(issuer),
|
|
196
|
+
audience: oauth2Client.clientId
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
const accessTokenResponse = yield jose.jwtVerify(
|
|
200
|
+
tokens.access_token,
|
|
201
|
+
JWKS,
|
|
202
|
+
{
|
|
203
|
+
issuer: getIssuerVariations(issuer)
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
return withoutUndefined({
|
|
207
|
+
id_token: idTokenResponse.payload,
|
|
208
|
+
access_token: accessTokenResponse.payload,
|
|
209
|
+
refresh_token: tokens.refresh_token
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/shared/session.ts
|
|
215
|
+
var _jwt = require('oslo/jwt');
|
|
216
|
+
function getUser(storage) {
|
|
217
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
218
|
+
var _a, _b;
|
|
219
|
+
const tokens = retrieveTokens(storage);
|
|
220
|
+
if (!tokens) return null;
|
|
221
|
+
return (_b = (_a = _jwt.parseJWT.call(void 0, tokens.id_token)) == null ? void 0 : _a.payload) != null ? _b : null;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/constants.ts
|
|
226
|
+
var DEFAULT_SCOPES = [
|
|
227
|
+
"openid",
|
|
228
|
+
"profile",
|
|
229
|
+
"email",
|
|
230
|
+
"forwardedTokens",
|
|
231
|
+
"offline_access"
|
|
232
|
+
];
|
|
233
|
+
var IFRAME_ID = "civic-auth-iframe";
|
|
234
|
+
var AUTH_SERVER = "https://auth-dev.civic.com/oauth";
|
|
235
|
+
var DEFAULT_OAUTH_GET_PARAMS = ["code", "state", "iss"];
|
|
236
|
+
|
|
237
|
+
// src/services/PKCE.ts
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// src/browser/storage.ts
|
|
241
|
+
var LocalStorageAdapter = class {
|
|
242
|
+
get(key) {
|
|
243
|
+
return localStorage.getItem(key) || "";
|
|
244
|
+
}
|
|
245
|
+
set(key, value) {
|
|
246
|
+
localStorage.setItem(key, value);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// src/services/PKCE.ts
|
|
251
|
+
var ConfidentialClientPKCEConsumer = class {
|
|
252
|
+
constructor(pkceChallengeEndpoint) {
|
|
253
|
+
this.pkceChallengeEndpoint = pkceChallengeEndpoint;
|
|
254
|
+
}
|
|
255
|
+
getCodeChallenge() {
|
|
256
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
257
|
+
const response = yield fetch(this.pkceChallengeEndpoint);
|
|
258
|
+
const data = yield response.json();
|
|
259
|
+
return data.challenge;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var GenericPublicClientPKCEProducer = class {
|
|
264
|
+
constructor(storage) {
|
|
265
|
+
this.storage = storage;
|
|
266
|
+
}
|
|
267
|
+
// if there is already a verifier, return it,
|
|
268
|
+
// If not, create a new one and store it
|
|
269
|
+
getCodeChallenge() {
|
|
270
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
271
|
+
const verifier = _oauth2.generateCodeVerifier.call(void 0, );
|
|
272
|
+
this.storage.set("code_verifier", verifier);
|
|
273
|
+
return deriveCodeChallenge(verifier);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// if there is already a verifier, return it,
|
|
277
|
+
getCodeVerifier() {
|
|
278
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
279
|
+
return this.storage.get("code_verifier");
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
var BrowserPublicClientPKCEProducer = class extends GenericPublicClientPKCEProducer {
|
|
284
|
+
constructor() {
|
|
285
|
+
super(new LocalStorageAdapter());
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// src/services/AuthenticationService.ts
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
// src/lib/windowUtil.ts
|
|
293
|
+
var isWindowInIframe = (window2) => {
|
|
294
|
+
var _a;
|
|
295
|
+
if (typeof window2 !== "undefined") {
|
|
296
|
+
try {
|
|
297
|
+
if (((_a = window2 == null ? void 0 : window2.frameElement) == null ? void 0 : _a.id) === "civic-auth-iframe") {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
} catch (_e) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
};
|
|
306
|
+
var removeParamsWithoutReload = (paramsToRemove) => {
|
|
307
|
+
const url = new URL(window.location.href);
|
|
308
|
+
paramsToRemove.forEach((param) => {
|
|
309
|
+
url.searchParams.delete(param);
|
|
310
|
+
});
|
|
311
|
+
window.history.replaceState({}, "", url);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/services/AuthenticationService.ts
|
|
315
|
+
var BrowserAuthenticationInitiator = class {
|
|
316
|
+
constructor(config) {
|
|
317
|
+
this.config = config;
|
|
318
|
+
}
|
|
319
|
+
// Use the config (Client ID, scopes OAuth Server, Endpoints, PKCEConsumer) to generate a new login url
|
|
320
|
+
// and then use the display mode to decide how to send the user there
|
|
321
|
+
signIn(iframeRef) {
|
|
322
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
323
|
+
const url = yield generateOauthLoginUrl(this.config);
|
|
324
|
+
if (this.config.displayMode === "iframe") {
|
|
325
|
+
if (!iframeRef)
|
|
326
|
+
throw new Error("iframeRef is required for displayMode 'iframe'");
|
|
327
|
+
iframeRef.setAttribute("src", url.toString());
|
|
328
|
+
}
|
|
329
|
+
if (this.config.displayMode === "redirect") {
|
|
330
|
+
window.location.href = url.toString();
|
|
331
|
+
}
|
|
332
|
+
if (this.config.displayMode === "new_tab") {
|
|
333
|
+
window.open(url.toString(), "_blank");
|
|
334
|
+
}
|
|
335
|
+
return url;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
signOut() {
|
|
339
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
340
|
+
const localStorage2 = new LocalStorageAdapter();
|
|
341
|
+
clearTokens(localStorage2);
|
|
342
|
+
clearUser(localStorage2);
|
|
343
|
+
const url = yield generateOauthLogoutUrl(this.config);
|
|
344
|
+
return url;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var GenericAuthenticationInitiator = class {
|
|
349
|
+
constructor(config) {
|
|
350
|
+
this.config = config;
|
|
351
|
+
}
|
|
352
|
+
// Use the config (Client ID, scopes OAuth Server, Endpoints, PKCEConsumer) to generate a new login url
|
|
353
|
+
// and simply return the url
|
|
354
|
+
signIn() {
|
|
355
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
356
|
+
return generateOauthLoginUrl(this.config);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
signOut() {
|
|
360
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
361
|
+
return generateOauthLogoutUrl(this.config);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
var BrowserAuthenticationService = class _BrowserAuthenticationService extends BrowserAuthenticationInitiator {
|
|
366
|
+
// TODO WIP - perhaps we want to keep resolver and initiator separate here
|
|
367
|
+
constructor(config, pkceProducer = new BrowserPublicClientPKCEProducer()) {
|
|
368
|
+
super(_chunkCRTRMMJ7js.__spreadProps.call(void 0, _chunkCRTRMMJ7js.__spreadValues.call(void 0, {}, config), {
|
|
369
|
+
state: generateState(config.displayMode),
|
|
370
|
+
// Store and retrieve the PKCE challenge in local storage
|
|
371
|
+
pkceConsumer: pkceProducer
|
|
372
|
+
}));
|
|
373
|
+
this.pkceProducer = pkceProducer;
|
|
374
|
+
}
|
|
375
|
+
// TODO too much code duplication here between the browser and the server variant.
|
|
376
|
+
// Suggestion for refactor: Standardise the config for AuthenticationResolvers and create a one-shot
|
|
377
|
+
// function for generating an oauth2client from it
|
|
378
|
+
init() {
|
|
379
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
380
|
+
this.endpoints = yield getEndpointsWithOverrides(
|
|
381
|
+
this.config.oauthServer,
|
|
382
|
+
this.config.endpointOverrides
|
|
383
|
+
);
|
|
384
|
+
this.oauth2client = new (0, _oauth2.OAuth2Client)(
|
|
385
|
+
this.config.clientId,
|
|
386
|
+
this.endpoints.auth,
|
|
387
|
+
this.endpoints.token,
|
|
388
|
+
{
|
|
389
|
+
redirectURI: this.config.redirectUrl
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
return this;
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// Two responsibilities:
|
|
396
|
+
// 1. resolve the auth code to get the tokens (should use library code)
|
|
397
|
+
// 2. store the tokens in local storage
|
|
398
|
+
tokenExchange(code, state) {
|
|
399
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
400
|
+
if (!this.oauth2client) yield this.init();
|
|
401
|
+
const codeVerifier = yield this.pkceProducer.getCodeVerifier();
|
|
402
|
+
if (!codeVerifier) throw new Error("Code verifier not found in storage");
|
|
403
|
+
const tokens = yield exchangeTokens(
|
|
404
|
+
code,
|
|
405
|
+
state,
|
|
406
|
+
this.pkceProducer,
|
|
407
|
+
this.oauth2client,
|
|
408
|
+
// clean up types here to avoid the ! operator
|
|
409
|
+
this.config.oauthServer,
|
|
410
|
+
this.endpoints
|
|
411
|
+
// clean up types here to avoid the ! operator
|
|
412
|
+
);
|
|
413
|
+
storeTokens(new LocalStorageAdapter(), tokens);
|
|
414
|
+
const parsedDisplayMode = displayModeFromState(
|
|
415
|
+
state,
|
|
416
|
+
this.config.displayMode
|
|
417
|
+
);
|
|
418
|
+
if (parsedDisplayMode === "new_tab") {
|
|
419
|
+
window.close();
|
|
420
|
+
} else if (parsedDisplayMode === "redirect") {
|
|
421
|
+
removeParamsWithoutReload(DEFAULT_OAUTH_GET_PARAMS);
|
|
422
|
+
}
|
|
423
|
+
return tokens;
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
// Get the session data from local storage
|
|
427
|
+
getSessionData() {
|
|
428
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
429
|
+
const storageData = retrieveTokens(new LocalStorageAdapter());
|
|
430
|
+
if (!storageData) return null;
|
|
431
|
+
return {
|
|
432
|
+
authenticated: !!storageData.id_token,
|
|
433
|
+
idToken: storageData.id_token,
|
|
434
|
+
accessToken: storageData.access_token,
|
|
435
|
+
refreshToken: storageData.refresh_token
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
validateExistingSession() {
|
|
440
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
441
|
+
try {
|
|
442
|
+
const sessionData = yield this.getSessionData();
|
|
443
|
+
if (!(sessionData == null ? void 0 : sessionData.idToken) || !sessionData.accessToken) {
|
|
444
|
+
const unAuthenticatedSession = _chunkCRTRMMJ7js.__spreadProps.call(void 0, _chunkCRTRMMJ7js.__spreadValues.call(void 0, {}, sessionData), { authenticated: false });
|
|
445
|
+
clearTokens(new LocalStorageAdapter());
|
|
446
|
+
return unAuthenticatedSession;
|
|
447
|
+
}
|
|
448
|
+
if (!this.endpoints || !this.oauth2client) yield this.init();
|
|
449
|
+
yield validateOauth2Tokens(
|
|
450
|
+
{
|
|
451
|
+
access_token: sessionData.accessToken,
|
|
452
|
+
id_token: sessionData.idToken,
|
|
453
|
+
refresh_token: sessionData.refreshToken
|
|
454
|
+
},
|
|
455
|
+
this.endpoints,
|
|
456
|
+
this.oauth2client,
|
|
457
|
+
this.config.oauthServer
|
|
458
|
+
);
|
|
459
|
+
return sessionData;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.warn("Failed to validate existing tokens", error);
|
|
462
|
+
const unAuthenticatedSession = {
|
|
463
|
+
authenticated: false
|
|
464
|
+
};
|
|
465
|
+
clearTokens(new LocalStorageAdapter());
|
|
466
|
+
return unAuthenticatedSession;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
static build(config) {
|
|
471
|
+
return _chunkCRTRMMJ7js.__async.call(void 0, this, null, function* () {
|
|
472
|
+
const resolver = new _BrowserAuthenticationService(config);
|
|
473
|
+
yield resolver.init();
|
|
474
|
+
return resolver;
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
exports.GenericUserSession = GenericUserSession; exports.DEFAULT_SCOPES = DEFAULT_SCOPES; exports.IFRAME_ID = IFRAME_ID; exports.AUTH_SERVER = AUTH_SERVER; exports.isWindowInIframe = isWindowInIframe; exports.generateState = generateState; exports.cn = cn; exports.withoutUndefined = withoutUndefined; exports.getEndpointsWithOverrides = getEndpointsWithOverrides; exports.exchangeTokens = exchangeTokens; exports.storeTokens = storeTokens; exports.clearTokens = clearTokens; exports.retrieveTokens = retrieveTokens; exports.LocalStorageAdapter = LocalStorageAdapter; exports.ConfidentialClientPKCEConsumer = ConfidentialClientPKCEConsumer; exports.GenericPublicClientPKCEProducer = GenericPublicClientPKCEProducer; exports.BrowserPublicClientPKCEProducer = BrowserPublicClientPKCEProducer; exports.BrowserAuthenticationInitiator = BrowserAuthenticationInitiator; exports.GenericAuthenticationInitiator = GenericAuthenticationInitiator; exports.BrowserAuthenticationService = BrowserAuthenticationService; exports.getUser = getUser;
|
|
502
|
+
//# sourceMappingURL=chunk-YNLXRD5L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/lucas/dev/civic/civic-auth/packages/civic-auth-client/dist/chunk-YNLXRD5L.js","../src/shared/types.ts","../src/shared/util.ts","../src/lib/oauth.ts","../src/utils.ts","../src/shared/UserSession.ts","../src/shared/session.ts","../src/constants.ts","../src/services/PKCE.ts","../src/browser/storage.ts","../src/services/AuthenticationService.ts","../src/lib/windowUtil.ts"],"names":["OAuthTokens","OAuth2Client","window","localStorage"],"mappings":"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","file":"/Users/lucas/dev/civic/civic-auth/packages/civic-auth-client/dist/chunk-YNLXRD5L.js","sourcesContent":[null,"export enum OAuthTokens {\n ID_TOKEN = \"id_token\",\n ACCESS_TOKEN = \"access_token\",\n REFRESH_TOKEN = \"refresh_token\",\n}\n\nexport enum UserStorage {\n USER = \"user\",\n}\n","// Utility functions shared by auth server and client integrations\n// Typically these functions should be used inside AuthenticationInitiator and AuthenticationResolver implementations\n\nimport {\n AuthStorage,\n Endpoints,\n JWTPayload,\n OIDCTokenResponseBody,\n ParsedTokens,\n} from \"@/types.js\";\nimport { OAuthTokens } from \"./types\";\nimport { OAuth2Client } from \"oslo/oauth2\";\nimport { getIssuerVariations, getOauthEndpoints } from \"@/lib/oauth.js\";\nimport * as jose from \"jose\";\nimport { withoutUndefined } from \"@/utils.js\";\nimport { PKCEConsumer, PKCEProducer } from \"@/services/types.js\";\nimport { GenericUserSession } from \"./UserSession\";\n\n/**\n * Given a PKCE code verifier, derive the code challenge using SHA\n */\nexport async function deriveCodeChallenge(\n codeVerifier: string,\n method: \"Plain\" | \"S256\" = \"S256\",\n): Promise<string> {\n if (method === \"Plain\") {\n console.warn(\"Using insecure plain code challenge method\");\n return codeVerifier;\n }\n\n const encoder = new TextEncoder();\n const data = encoder.encode(codeVerifier);\n const digest = await crypto.subtle.digest(\"SHA-256\", data);\n return btoa(String.fromCharCode(...new Uint8Array(digest)))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport async function getEndpointsWithOverrides(\n oauthServer: string,\n endpointOverrides: Partial<Endpoints> = {},\n) {\n const endpoints = await getOauthEndpoints(oauthServer);\n return {\n ...endpoints,\n ...endpointOverrides,\n };\n}\n\nexport async function generateOauthLoginUrl(config: {\n clientId: string;\n scopes: string[];\n state: string;\n redirectUrl: string;\n oauthServer: string;\n nonce?: string;\n endpointOverrides?: Partial<Endpoints>;\n // used to get the PKCE challenge\n pkceConsumer: PKCEConsumer;\n}): Promise<URL> {\n const endpoints = await getEndpointsWithOverrides(\n config.oauthServer,\n config.endpointOverrides,\n );\n const oauth2Client = buildOauth2Client(\n config.clientId,\n config.redirectUrl,\n endpoints,\n );\n const challenge = await config.pkceConsumer.getCodeChallenge();\n const oAuthUrl = await oauth2Client.createAuthorizationURL({\n state: config.state,\n scopes: config.scopes,\n });\n // The OAuth2 client supports PKCE, but does not allow passing in a code challenge from some other source\n // It only allows passing in a code verifier which it then hashes itself.\n oAuthUrl.searchParams.append(\"code_challenge\", challenge);\n oAuthUrl.searchParams.append(\"code_challenge_method\", \"S256\");\n if (config.nonce) {\n // nonce isn't supported by oslo, so we add it manually\n oAuthUrl.searchParams.append(\"nonce\", config.nonce);\n }\n // Required by the auth server for offline_access scope\n oAuthUrl.searchParams.append(\"prompt\", \"consent\");\n\n console.log(\"Generated OAuth URL\", oAuthUrl.toString());\n return oAuthUrl;\n}\n\nexport async function generateOauthLogoutUrl(config: {\n clientId: string;\n scopes: string[];\n oauthServer: string;\n endpointOverrides?: Partial<Endpoints>;\n // used to get the PKCE challenge\n pkceConsumer: PKCEConsumer;\n}): Promise<URL> {\n // TODO\n return new URL(\"http://localhost\");\n}\n\nexport function buildOauth2Client(\n clientId: string,\n redirectUri: string,\n endpoints: Endpoints,\n): OAuth2Client {\n return new OAuth2Client(clientId, endpoints.auth, endpoints.token, {\n redirectURI: redirectUri,\n });\n}\n\nexport async function exchangeTokens(\n code: string,\n state: string,\n pkceProducer: PKCEProducer,\n oauth2Client: OAuth2Client,\n oauthServer: string,\n endpoints: Endpoints,\n) {\n const codeVerifier = await pkceProducer.getCodeVerifier();\n if (!codeVerifier) throw new Error(\"Code verifier not found in state\");\n\n const tokens =\n await oauth2Client.validateAuthorizationCode<OIDCTokenResponseBody>(code, {\n codeVerifier,\n });\n\n // Validate relevant tokens\n try {\n await validateOauth2Tokens(tokens, endpoints, oauth2Client, oauthServer);\n } catch (error) {\n console.error(\"tokenExchange error\", { error, tokens });\n throw new Error(\n `OIDC tokens validation failed: ${(error as Error).message}`,\n );\n }\n\n return tokens;\n}\n\nexport function storeTokens(\n storage: AuthStorage,\n tokens: OIDCTokenResponseBody,\n) {\n // store tokens in storage ( TODO we should probably store them against the state to allow multiple logins )\n storage.set(OAuthTokens.ID_TOKEN, tokens.id_token);\n storage.set(OAuthTokens.ACCESS_TOKEN, tokens.access_token);\n if (tokens.refresh_token)\n storage.set(OAuthTokens.REFRESH_TOKEN, tokens.refresh_token);\n}\n\nexport function clearTokens(storage: AuthStorage) {\n Object.values(OAuthTokens).forEach((cookie) => {\n storage.set(cookie, \"\");\n });\n}\nexport function clearUser(storage: AuthStorage) {\n const userSession = new GenericUserSession(storage);\n userSession.set(null);\n}\n\nexport function retrieveTokens(\n storage: AuthStorage,\n): OIDCTokenResponseBody | null {\n const idToken = storage.get(OAuthTokens.ID_TOKEN);\n const accessToken = storage.get(OAuthTokens.ACCESS_TOKEN);\n const refreshToken = storage.get(OAuthTokens.REFRESH_TOKEN);\n\n if (!idToken || !accessToken) return null;\n\n return {\n id_token: idToken,\n access_token: accessToken,\n refresh_token: refreshToken ?? undefined,\n };\n}\n\nexport async function validateOauth2Tokens(\n tokens: OIDCTokenResponseBody,\n endpoints: Endpoints,\n oauth2Client: OAuth2Client,\n issuer: string,\n): Promise<ParsedTokens> {\n const JWKS = jose.createRemoteJWKSet(new URL(endpoints.jwks));\n\n // validate the ID token\n const idTokenResponse = await jose.jwtVerify<JWTPayload>(\n tokens.id_token,\n JWKS,\n {\n issuer: getIssuerVariations(issuer),\n audience: oauth2Client.clientId,\n },\n );\n\n // validate the access token\n const accessTokenResponse = await jose.jwtVerify<JWTPayload>(\n tokens.access_token,\n JWKS,\n {\n issuer: getIssuerVariations(issuer),\n },\n );\n\n return withoutUndefined({\n id_token: idTokenResponse.payload,\n access_token: accessTokenResponse.payload,\n refresh_token: tokens.refresh_token,\n });\n}\n","import { DisplayMode, Endpoints, OpenIdConfiguration } from \"@/types\";\nimport { v4 as uuid } from \"uuid\";\n\nconst getIssuerVariations = (issuer: string): string[] => {\n const issuerWithoutSlash = issuer.endsWith(\"/\")\n ? issuer.slice(0, issuer.length - 1)\n : issuer;\n\n const issuerWithSlash = `${issuerWithoutSlash}/`;\n\n return [issuerWithoutSlash, issuerWithSlash];\n};\n\nconst addSlashIfNeeded = (url: string): string =>\n url.endsWith(\"/\") ? url : `${url}/`;\n\nconst getOauthEndpoints = async (oauthServer: string): Promise<Endpoints> => {\n const openIdConfigResponse = await fetch(\n `${addSlashIfNeeded(oauthServer)}.well-known/openid-configuration`,\n );\n const openIdConfig =\n (await openIdConfigResponse.json()) as OpenIdConfiguration;\n return {\n jwks: openIdConfig.jwks_uri,\n auth: openIdConfig.authorization_endpoint,\n token: openIdConfig.token_endpoint,\n userinfo: openIdConfig.userinfo_endpoint,\n };\n};\n\n/**\n * creates a state string for the OAuth2 flow, encoding the display mode too for future use\n * @param {DisplayMode} displayMode\n * @returns {string}\n */\nconst generateState = (displayMode: DisplayMode): string => {\n const jsonString = JSON.stringify({\n uuid: uuid(),\n displayMode,\n });\n return btoa(jsonString);\n};\n\n/**\n * parses the state string from the OAuth2 flow, decoding the display mode too\n * @param state\n * @param sessionDisplayMode\n * @returns { uuid: string, displayMode: DisplayMode }\n */\nconst displayModeFromState = (\n state: string,\n sessionDisplayMode: DisplayMode | undefined,\n): DisplayMode | undefined => {\n try {\n const jsonString = atob(state);\n return JSON.parse(jsonString).displayMode;\n } catch (e) {\n console.error(\"Failed to parse displayMode from state:\", state);\n return sessionDisplayMode;\n }\n};\n\nexport {\n getIssuerVariations,\n getOauthEndpoints,\n displayModeFromState,\n generateState,\n};\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Checks if a popup window is blocked by the browser.\n *\n * This function attempts to open a small popup window and then checks if it was successfully created.\n * If the popup is blocked by the browser, the function returns `true`. Otherwise, it returns `false`.\n *\n * @returns {boolean} - `true` if the popup is blocked, `false` otherwise.\n */\nconst isPopupBlocked = (): boolean => {\n // First we try to open a small popup window. It either returns a window object or null.\n const popup = window.open(\"\", \"\", \"width=1,height=1\");\n\n // If window.open() returns null, popup is definitely blocked\n if (!popup) {\n return true;\n }\n\n try {\n // Try to access a property of the popup to check if it's usable\n if (typeof popup.closed === \"undefined\") {\n throw new Error(\"Popup is blocked\");\n }\n } catch {\n // Accessing the popup's properties throws an error if the popup is blocked\n return true;\n }\n\n // Close the popup immediately if it was opened\n popup.close();\n return false;\n};\n\nconst cn = (...inputs: ClassValue[]) => {\n return twMerge(clsx(inputs));\n};\n\n// This type narrows T as far as it can by:\n// - removing all keys where the value is `undefined`\n// - making keys that are not undefined required\n// So, for example: given { a: string | undefined, b: string | undefined },\n// if you pass in { a: \"foo\" }, it returns an object of type: { a: string }\ntype WithoutUndefined<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K];\n};\nexport const withoutUndefined = <T extends { [K in keyof T]: unknown }>(\n obj: T,\n): WithoutUndefined<T> => {\n const result = {} as WithoutUndefined<T>;\n\n for (const key in obj) {\n if (obj[key] !== undefined) {\n // TypeScript needs assurance that key is a valid key in WithoutUndefined<T>\n // We use type assertion here\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (result as any)[key] = obj[key];\n }\n }\n\n return result;\n};\n\nexport { cn, isPopupBlocked };\n","import { AuthStorage, User } from \"@/types\";\nimport { UserStorage } from \"./types\";\n\nexport interface UserSession {\n get(): User | null;\n set(user: User): void;\n}\n\nexport class GenericUserSession implements UserSession {\n constructor(readonly storage: AuthStorage) {}\n\n get(): User | null {\n const user = this.storage.get(UserStorage.USER);\n return user ? JSON.parse(user) : null;\n }\n\n set(user: User | null): void {\n const value = user ? JSON.stringify(user) : \"\";\n this.storage.set(UserStorage.USER, value);\n }\n}\n","import { retrieveTokens } from \"@/shared/util.js\";\nimport { parseJWT } from \"oslo/jwt\";\nimport { AuthStorage, User } from \"@/types.js\";\n\nexport async function getUser(storage: AuthStorage): Promise<User | null> {\n const tokens = retrieveTokens(storage);\n if (!tokens) return null;\n\n // Assumes all information is in the ID token\n return (parseJWT(tokens.id_token)?.payload as User) ?? null;\n}\n","const DEFAULT_SCOPES = [\n \"openid\",\n \"profile\",\n \"email\",\n \"forwardedTokens\",\n \"offline_access\",\n];\nconst IFRAME_ID = \"civic-auth-iframe\";\n\nconst AUTH_SERVER = \"https://auth-dev.civic.com/oauth\";\n\nconst DEFAULT_OAUTH_GET_PARAMS = [\"code\", \"state\", \"iss\"];\n\nexport { DEFAULT_SCOPES, DEFAULT_OAUTH_GET_PARAMS, IFRAME_ID, AUTH_SERVER };\n","import { deriveCodeChallenge } from \"@/shared/util.js\";\nimport { generateCodeVerifier } from \"oslo/oauth2\";\nimport { LocalStorageAdapter } from \"@/browser/storage.js\";\nimport { PKCEConsumer, PKCEProducer } from \"@/services/types.ts\";\nimport { AuthStorage } from \"@/types\";\n\n/** A PKCE consumer that retrieves the challenge from a server endpoint */\nexport class ConfidentialClientPKCEConsumer implements PKCEConsumer {\n constructor(private pkceChallengeEndpoint: string) {}\n async getCodeChallenge(): Promise<string> {\n const response = await fetch(this.pkceChallengeEndpoint);\n const data = (await response.json()) as { challenge: string };\n return data.challenge;\n }\n}\n\n/** A PKCE Producer that can generate and store a code verifier, but is agnostic as to the storage location */\nexport class GenericPublicClientPKCEProducer implements PKCEProducer {\n constructor(private storage: AuthStorage) {}\n\n // if there is already a verifier, return it,\n // If not, create a new one and store it\n async getCodeChallenge(): Promise<string> {\n // let verifier = await this.getCodeVerifier();\n // if (!verifier) {\n const verifier = generateCodeVerifier();\n this.storage.set(\"code_verifier\", verifier);\n // }\n return deriveCodeChallenge(verifier);\n }\n // if there is already a verifier, return it,\n async getCodeVerifier(): Promise<string | null> {\n return this.storage.get(\"code_verifier\");\n }\n}\n\n/** A PKCE Producer that is expected to run on a browser, and does not need a backend */\nexport class BrowserPublicClientPKCEProducer extends GenericPublicClientPKCEProducer {\n constructor() {\n super(new LocalStorageAdapter());\n }\n}\n","import { AuthStorage } from \"@/types\";\n\nexport class LocalStorageAdapter implements AuthStorage {\n get(key: string): string {\n return localStorage.getItem(key) || \"\";\n }\n\n set(key: string, value: string): void {\n localStorage.setItem(key, value);\n }\n}\n","// Proposals for revised versions of the SessionService AKA AuthSessionService\n\nimport {\n DisplayMode,\n Endpoints,\n OIDCTokenResponseBody,\n SessionData,\n} from \"@/types.js\";\nimport { BrowserPublicClientPKCEProducer } from \"@/services/PKCE.js\";\nimport {\n clearTokens,\n clearUser,\n exchangeTokens,\n generateOauthLoginUrl,\n generateOauthLogoutUrl,\n getEndpointsWithOverrides,\n retrieveTokens,\n storeTokens,\n validateOauth2Tokens,\n} from \"@/shared/util.js\";\nimport { displayModeFromState, generateState } from \"@/lib/oauth.js\";\nimport { OAuth2Client } from \"oslo/oauth2\";\nimport { LocalStorageAdapter } from \"@/browser/storage.js\";\nimport {\n AuthenticationInitiator,\n AuthenticationResolver,\n PKCEConsumer,\n} from \"@/services/types.js\";\nimport { removeParamsWithoutReload } from \"@/lib/windowUtil\";\nimport { DEFAULT_OAUTH_GET_PARAMS } from \"@/constants\";\n\n/**\n * An authentication initiator that works on a browser. Since this is just triggering\n * login and logout, session data is not stored here.\n * An associated AuthenticationResolver would be needed to get the session data.\n * Storage is needed for the code verifier, this is the domain of the PKCEConsumer\n * The storage used by the PKCEConsumer should be available to the AuthenticationResolver.\n *\n * Example usage:\n *\n * 1) Client-only SPA -eg a react app with no server:\n * new BrowserAuthenticationInitiator({\n * pkceConsumer: new BrowserPublicClientPKCEProducer(), // generate and retrieve the challenge client-side\n * ... other config\n * })\n *\n * 2) Client-side of a client/server app - eg a react app with a backend:\n * new BrowserAuthenticationInitiator({\n * pkceConsumer: new ConfidentialClientPKCEConsumer(\"https://myserver.com/pkce\"), // get the challenge from the server\n * ... other config\n * })\n */\nexport class BrowserAuthenticationInitiator implements AuthenticationInitiator {\n protected config: {\n clientId: string;\n redirectUrl: string;\n state: string;\n scopes: string[];\n // determines whether to trigger the login/logout in an iframe, a new browser window, or redirect the current one.\n displayMode: DisplayMode;\n oauthServer: string;\n // the endpoints to use for the login (if not obtained from the auth server\n endpointOverrides?: Partial<Endpoints>;\n // used to get the PKCE challenge\n pkceConsumer: PKCEConsumer;\n // the nonce to use for the login\n nonce?: string;\n };\n\n constructor(config: typeof this.config) {\n this.config = config;\n }\n // Use the config (Client ID, scopes OAuth Server, Endpoints, PKCEConsumer) to generate a new login url\n // and then use the display mode to decide how to send the user there\n async signIn(iframeRef: HTMLIFrameElement | null): Promise<URL> {\n const url = await generateOauthLoginUrl(this.config);\n\n if (this.config.displayMode === \"iframe\") {\n if (!iframeRef)\n throw new Error(\"iframeRef is required for displayMode 'iframe'\");\n iframeRef.setAttribute(\"src\", url.toString());\n }\n if (this.config.displayMode === \"redirect\") {\n window.location.href = url.toString();\n }\n if (this.config.displayMode === \"new_tab\") {\n window.open(url.toString(), \"_blank\");\n }\n return url;\n }\n\n async signOut(): Promise<URL> {\n const localStorage = new LocalStorageAdapter();\n clearTokens(localStorage);\n clearUser(localStorage);\n // TODO open the iframe or new tab etc: the logout URL is not currently\n // supported by on the oauth, so just clear state until then\n const url = await generateOauthLogoutUrl(this.config);\n return url;\n }\n}\n\n/** A general-purpose authentication initiator, that just generates urls, but lets\n * the caller decide how to use them. This is useful for server-side applications\n * that may serve this URL to their front-ends or just call them directly\n */\nexport class GenericAuthenticationInitiator implements AuthenticationInitiator {\n protected config: {\n clientId: string;\n redirectUrl: string;\n state: string;\n scopes: string[];\n oauthServer: string;\n nonce?: string;\n // the endpoints to use for the login (if not obtained from the auth server)\n endpointOverrides?: Partial<Endpoints>;\n // used to get the PKCE challenge\n pkceConsumer: PKCEConsumer;\n };\n\n constructor(config: typeof this.config) {\n this.config = config;\n }\n\n // Use the config (Client ID, scopes OAuth Server, Endpoints, PKCEConsumer) to generate a new login url\n // and simply return the url\n async signIn(): Promise<URL> {\n return generateOauthLoginUrl(this.config);\n }\n\n async signOut(): Promise<URL> {\n return generateOauthLogoutUrl(this.config);\n }\n}\n\ntype BrowserAuthenticationConfig = {\n clientId: string;\n redirectUrl: string;\n scopes: string[];\n oauthServer: string;\n endpointOverrides?: Partial<Endpoints>;\n displayMode: DisplayMode;\n};\n\n/**\n * An authentication resolver that can run on the browser (i.e. a public client)\n * It uses PKCE for security. PKCE and Session data are stored in local storage\n */\nexport class BrowserAuthenticationService extends BrowserAuthenticationInitiator {\n private oauth2client: OAuth2Client | undefined;\n private endpoints: Endpoints | undefined;\n\n // TODO WIP - perhaps we want to keep resolver and initiator separate here\n constructor(\n config: BrowserAuthenticationConfig,\n // Since we are running fully on the client, we produce as well as consume the PKCE challenge\n protected pkceProducer = new BrowserPublicClientPKCEProducer(),\n ) {\n super({\n ...config,\n state: generateState(config.displayMode),\n // Store and retrieve the PKCE challenge in local storage\n pkceConsumer: pkceProducer,\n });\n }\n\n // TODO too much code duplication here between the browser and the server variant.\n // Suggestion for refactor: Standardise the config for AuthenticationResolvers and create a one-shot\n // function for generating an oauth2client from it\n async init(): Promise<this> {\n // resolve oauth config\n this.endpoints = await getEndpointsWithOverrides(\n this.config.oauthServer,\n this.config.endpointOverrides,\n );\n this.oauth2client = new OAuth2Client(\n this.config.clientId,\n this.endpoints.auth,\n this.endpoints.token,\n {\n redirectURI: this.config.redirectUrl,\n },\n );\n\n return this;\n }\n\n // Two responsibilities:\n // 1. resolve the auth code to get the tokens (should use library code)\n // 2. store the tokens in local storage\n async tokenExchange(\n code: string,\n state: string,\n ): Promise<OIDCTokenResponseBody> {\n if (!this.oauth2client) await this.init();\n const codeVerifier = await this.pkceProducer.getCodeVerifier();\n if (!codeVerifier) throw new Error(\"Code verifier not found in storage\");\n\n // exchange auth code for tokens\n const tokens = await exchangeTokens(\n code,\n state,\n this.pkceProducer,\n this.oauth2client!, // clean up types here to avoid the ! operator\n this.config.oauthServer,\n this.endpoints!, // clean up types here to avoid the ! operator\n );\n\n storeTokens(new LocalStorageAdapter(), tokens);\n\n // cleanup the browser window if needed\n const parsedDisplayMode = displayModeFromState(\n state,\n this.config.displayMode,\n );\n\n if (parsedDisplayMode === \"new_tab\") {\n // Close the popup window\n window.close();\n } else if (parsedDisplayMode === \"redirect\") {\n // these are the default oAuth params that get added to the URL which we want to remove\n removeParamsWithoutReload(DEFAULT_OAUTH_GET_PARAMS);\n }\n return tokens;\n }\n\n // Get the session data from local storage\n async getSessionData(): Promise<SessionData | null> {\n const storageData = retrieveTokens(new LocalStorageAdapter());\n\n if (!storageData) return null;\n\n return {\n authenticated: !!storageData.id_token,\n idToken: storageData.id_token,\n accessToken: storageData.access_token,\n refreshToken: storageData.refresh_token,\n };\n }\n\n async validateExistingSession(): Promise<SessionData> {\n try {\n const sessionData = await this.getSessionData();\n if (!sessionData?.idToken || !sessionData.accessToken) {\n const unAuthenticatedSession = { ...sessionData, authenticated: false };\n clearTokens(new LocalStorageAdapter());\n return unAuthenticatedSession;\n }\n if (!this.endpoints || !this.oauth2client) await this.init();\n\n // this function will throw if any of the tokens are invalid\n await validateOauth2Tokens(\n {\n access_token: sessionData.accessToken,\n id_token: sessionData.idToken,\n refresh_token: sessionData.refreshToken,\n },\n this.endpoints!,\n this.oauth2client!,\n this.config.oauthServer,\n );\n return sessionData;\n } catch (error) {\n console.warn(\"Failed to validate existing tokens\", error);\n const unAuthenticatedSession = {\n authenticated: false,\n };\n clearTokens(new LocalStorageAdapter());\n return unAuthenticatedSession;\n }\n }\n\n static async build(\n config: BrowserAuthenticationConfig,\n ): Promise<AuthenticationResolver> {\n const resolver = new BrowserAuthenticationService(config);\n await resolver.init();\n\n return resolver;\n }\n}\n","const isWindowInIframe = (window: Window): boolean => {\n if (typeof window !== \"undefined\") {\n // use the window width to determine if we're in an iframe or not\n try {\n if (window?.frameElement?.id === \"civic-auth-iframe\") {\n return true;\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_e) {\n // If we get an error, we're not in an iframe\n return false;\n }\n }\n return false;\n};\n\nconst removeParamsWithoutReload = (paramsToRemove: string[]) => {\n const url = new URL(window.location.href);\n paramsToRemove.forEach((param: string) => {\n url.searchParams.delete(param);\n });\n window.history.replaceState({}, \"\", url);\n};\n\nexport { isWindowInIframe, removeParamsWithoutReload };\n"]}
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { TokenResponseBody } from 'oslo/oauth2';
|
|
2
|
-
import { JWT } from 'oslo/jwt';
|
|
3
2
|
|
|
4
3
|
type UnknownObject = Record<string, unknown>;
|
|
5
4
|
type EmptyObject = Record<string, never>;
|
|
6
5
|
type DisplayMode = "iframe" | "redirect" | "new_tab" | "custom_tab";
|
|
7
|
-
interface AuthSessionService {
|
|
8
|
-
loadAuthorizationUrl(authorizationURL: string, displayMode: DisplayMode): void;
|
|
9
|
-
getAuthorizationUrl(scopes: string[], overrideDisplayMode: DisplayMode, nonce?: string): Promise<string>;
|
|
10
|
-
signIn(displayMode: DisplayMode, scopes: string[], nonce?: string): Promise<void>;
|
|
11
|
-
tokenExchange(responseUrl: string): Promise<SessionData>;
|
|
12
|
-
getSessionData(): SessionData;
|
|
13
|
-
updateSessionData(data: SessionData): void;
|
|
14
|
-
getUserInfoService(): Promise<UserInfoService>;
|
|
15
|
-
}
|
|
16
|
-
interface UserInfoService {
|
|
17
|
-
getUserInfo<T extends UnknownObject>(accessToken: string, idToken: string | null): Promise<User<T> | null>;
|
|
18
|
-
}
|
|
19
6
|
type Endpoints = {
|
|
20
7
|
jwks: string;
|
|
21
8
|
auth: string;
|
|
@@ -42,23 +29,11 @@ type SessionData = {
|
|
|
42
29
|
type OIDCTokenResponseBody = TokenResponseBody & {
|
|
43
30
|
id_token: string;
|
|
44
31
|
};
|
|
45
|
-
type ParsedTokens = {
|
|
46
|
-
id_token: JWTPayload;
|
|
47
|
-
access_token: JWTPayload;
|
|
48
|
-
refresh_token?: string;
|
|
49
|
-
};
|
|
50
32
|
type ForwardedTokens = Record<string, {
|
|
51
33
|
idToken?: string;
|
|
52
34
|
accessToken?: string;
|
|
53
35
|
refreshToken?: string;
|
|
54
36
|
}>;
|
|
55
|
-
type JWTPayload = JWT["payload"] & {
|
|
56
|
-
iss: string;
|
|
57
|
-
aud: string;
|
|
58
|
-
sub: string;
|
|
59
|
-
iat: number;
|
|
60
|
-
exp: number;
|
|
61
|
-
};
|
|
62
37
|
type Tokens = {
|
|
63
38
|
idToken: string;
|
|
64
39
|
accessToken: string;
|
|
@@ -75,5 +50,9 @@ type BaseUser = {
|
|
|
75
50
|
updated_at?: Date;
|
|
76
51
|
};
|
|
77
52
|
type User<T extends UnknownObject = EmptyObject> = BaseUser & Tokens & T;
|
|
53
|
+
interface AuthStorage {
|
|
54
|
+
get(key: string): string | null;
|
|
55
|
+
set(key: string, value: string): void;
|
|
56
|
+
}
|
|
78
57
|
|
|
79
|
-
export type {
|
|
58
|
+
export type { AuthStorage as A, Config as C, DisplayMode as D, Endpoints as E, ForwardedTokens as F, OIDCTokenResponseBody as O, SessionData as S, Tokens as T, User as U, UnknownObject as a };
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { TokenResponseBody } from 'oslo/oauth2';
|
|
2
|
-
import { JWT } from 'oslo/jwt';
|
|
3
2
|
|
|
4
3
|
type UnknownObject = Record<string, unknown>;
|
|
5
4
|
type EmptyObject = Record<string, never>;
|
|
6
5
|
type DisplayMode = "iframe" | "redirect" | "new_tab" | "custom_tab";
|
|
7
|
-
interface AuthSessionService {
|
|
8
|
-
loadAuthorizationUrl(authorizationURL: string, displayMode: DisplayMode): void;
|
|
9
|
-
getAuthorizationUrl(scopes: string[], overrideDisplayMode: DisplayMode, nonce?: string): Promise<string>;
|
|
10
|
-
signIn(displayMode: DisplayMode, scopes: string[], nonce?: string): Promise<void>;
|
|
11
|
-
tokenExchange(responseUrl: string): Promise<SessionData>;
|
|
12
|
-
getSessionData(): SessionData;
|
|
13
|
-
updateSessionData(data: SessionData): void;
|
|
14
|
-
getUserInfoService(): Promise<UserInfoService>;
|
|
15
|
-
}
|
|
16
|
-
interface UserInfoService {
|
|
17
|
-
getUserInfo<T extends UnknownObject>(accessToken: string, idToken: string | null): Promise<User<T> | null>;
|
|
18
|
-
}
|
|
19
6
|
type Endpoints = {
|
|
20
7
|
jwks: string;
|
|
21
8
|
auth: string;
|
|
@@ -42,23 +29,11 @@ type SessionData = {
|
|
|
42
29
|
type OIDCTokenResponseBody = TokenResponseBody & {
|
|
43
30
|
id_token: string;
|
|
44
31
|
};
|
|
45
|
-
type ParsedTokens = {
|
|
46
|
-
id_token: JWTPayload;
|
|
47
|
-
access_token: JWTPayload;
|
|
48
|
-
refresh_token?: string;
|
|
49
|
-
};
|
|
50
32
|
type ForwardedTokens = Record<string, {
|
|
51
33
|
idToken?: string;
|
|
52
34
|
accessToken?: string;
|
|
53
35
|
refreshToken?: string;
|
|
54
36
|
}>;
|
|
55
|
-
type JWTPayload = JWT["payload"] & {
|
|
56
|
-
iss: string;
|
|
57
|
-
aud: string;
|
|
58
|
-
sub: string;
|
|
59
|
-
iat: number;
|
|
60
|
-
exp: number;
|
|
61
|
-
};
|
|
62
37
|
type Tokens = {
|
|
63
38
|
idToken: string;
|
|
64
39
|
accessToken: string;
|
|
@@ -75,5 +50,9 @@ type BaseUser = {
|
|
|
75
50
|
updated_at?: Date;
|
|
76
51
|
};
|
|
77
52
|
type User<T extends UnknownObject = EmptyObject> = BaseUser & Tokens & T;
|
|
53
|
+
interface AuthStorage {
|
|
54
|
+
get(key: string): string | null;
|
|
55
|
+
set(key: string, value: string): void;
|
|
56
|
+
}
|
|
78
57
|
|
|
79
|
-
export type {
|
|
58
|
+
export type { AuthStorage as A, Config as C, DisplayMode as D, Endpoints as E, ForwardedTokens as F, OIDCTokenResponseBody as O, SessionData as S, Tokens as T, User as U, UnknownObject as a };
|