@leaguebrain/provenance 0.0.0-stage → 0.14.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.
@@ -0,0 +1,74 @@
1
+ // src/initiatives/delete-session-cookies.ts
2
+ var deleteSessionCookies = (createContext) => {
3
+ return async (event) => {
4
+ const context = createContext(event);
5
+ context.session.deleteCookie();
6
+ };
7
+ };
8
+
9
+ // src/initiatives/protect-route.ts
10
+ var protectRoute = (createContext) => {
11
+ return async (event) => {
12
+ const context = createContext(event);
13
+ const session = context.locals.session;
14
+ if (session === null) {
15
+ return context.routes.login.redirect();
16
+ }
17
+ return session;
18
+ };
19
+ };
20
+
21
+ // src/initiatives/refresh.ts
22
+ var refresh = (createContext) => {
23
+ return async (event) => {
24
+ const context = createContext(event);
25
+ const session = context.locals.session;
26
+ if (session?.refreshToken) {
27
+ try {
28
+ const newTokens = await context.oauth.refresh(session.refreshToken);
29
+ const newSession = context.session.create(newTokens);
30
+ context.session.deleteCookie();
31
+ context.session.setCookie(newSession);
32
+ context.locals.session = newSession;
33
+ } catch (e) {
34
+ console.error("provenance:", e);
35
+ context.session.deleteCookie();
36
+ context.locals.session = null;
37
+ context.routes.login.redirect();
38
+ }
39
+ }
40
+ };
41
+ };
42
+
43
+ // src/initiatives/login.ts
44
+ var login = (createContext) => {
45
+ return async (event) => {
46
+ const context = createContext(event);
47
+ context.routes.login.redirect();
48
+ };
49
+ };
50
+
51
+ // src/initiatives/signup.ts
52
+ var signup = (createContext) => {
53
+ return async (event) => {
54
+ const context = createContext(event);
55
+ context.routes.signup.redirect();
56
+ };
57
+ };
58
+
59
+ // src/initiatives/logout.ts
60
+ var logout = (createContext) => {
61
+ return async (event) => {
62
+ const context = createContext(event);
63
+ context.routes.logout.redirect();
64
+ };
65
+ };
66
+ export {
67
+ deleteSessionCookies,
68
+ login,
69
+ logout,
70
+ protectRoute,
71
+ refresh,
72
+ signup
73
+ };
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/initiatives/delete-session-cookies.ts","../../src/initiatives/protect-route.ts","../../src/initiatives/refresh.ts","../../src/initiatives/login.ts","../../src/initiatives/signup.ts","../../src/initiatives/logout.ts"],"sourcesContent":["import { Context } from '../types';\n\nexport const deleteSessionCookies = <RequestEvent, ProviderSession, AppSession>(\n\tcreateContext: (event: RequestEvent) => Context<ProviderSession, AppSession>\n) => {\n\treturn async (event: RequestEvent) => {\n\t\tconst context = createContext(event);\n\n\t\tcontext.session.deleteCookie();\n\t};\n};\n","import { Context } from '../types';\n\nexport const protectRoute = <RequestEvent, ProviderSession, AppSession>(\n\tcreateContext: (event: RequestEvent) => Context<ProviderSession, AppSession>\n) => {\n\treturn async (event: RequestEvent) => {\n\t\tconst context = createContext(event);\n\t\tconst session = context.locals.session;\n\n\t\tif (session === null) {\n\t\t\treturn context.routes.login.redirect();\n\t\t}\n\n\t\treturn session;\n\t};\n};\n","import { Context } from '../types';\n\nexport const refresh = <RequestEvent, ProviderSession, AppSession extends { refreshToken: string }>(\n\tcreateContext: (event: RequestEvent) => Context<ProviderSession, AppSession>\n) => {\n\treturn async (event: RequestEvent) => {\n\t\tconst context = createContext(event);\n\t\tconst session = context.locals.session;\n\n\t\tif (session?.refreshToken) {\n\t\t\ttry {\n\t\t\t\t// the access token has expired, so refresh with the refresh token\n\t\t\t\tconst newTokens = await context.oauth.refresh(session.refreshToken);\n\t\t\t\tconst newSession = context.session.create(newTokens);\n\n\t\t\t\tcontext.session.deleteCookie();\n\t\t\t\tcontext.session.setCookie(newSession);\n\t\t\t\t// the runtime stores the provider session in locals (AppSession is its typed view)\n\t\t\t\tcontext.locals.session = newSession as unknown as AppSession;\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error('provenance:', e);\n\t\t\t\t// delete the session cookie\n\t\t\t\tcontext.session.deleteCookie();\n\t\t\t\tcontext.locals.session = null;\n\t\t\t\t// redirect to login\n\t\t\t\tcontext.routes.login.redirect();\n\t\t\t}\n\t\t}\n\t};\n};\n","import { Context } from '../types';\n\nexport const login = <RequestEvent, ProviderSession, AppSession>(\n\tcreateContext: (event: RequestEvent) => Context<ProviderSession, AppSession>\n) => {\n\treturn async (event: RequestEvent) => {\n\t\tconst context = createContext(event);\n\t\tcontext.routes.login.redirect();\n\t};\n};\n","import { Context } from '../types';\n\nexport const signup = <RequestEvent, ProviderSession, AppSession>(\n\tcreateContext: (event: RequestEvent) => Context<ProviderSession, AppSession>\n) => {\n\treturn async (event: RequestEvent) => {\n\t\tconst context = createContext(event);\n\t\tcontext.routes.signup.redirect();\n\t};\n};\n","import { Context } from '../types';\n\nexport const logout = <RequestEvent, ProviderSession, AppSession>(\n\tcreateContext: (event: RequestEvent) => Context<ProviderSession, AppSession>\n) => {\n\treturn async (event: RequestEvent) => {\n\t\tconst context = createContext(event);\n\t\tcontext.routes.logout.redirect();\n\t};\n};\n"],"mappings":";AAEO,IAAM,uBAAuB,CACnC,kBACI;AACJ,SAAO,OAAO,UAAwB;AACrC,UAAM,UAAU,cAAc,KAAK;AAEnC,YAAQ,QAAQ,aAAa;AAAA,EAC9B;AACD;;;ACRO,IAAM,eAAe,CAC3B,kBACI;AACJ,SAAO,OAAO,UAAwB;AACrC,UAAM,UAAU,cAAc,KAAK;AACnC,UAAM,UAAU,QAAQ,OAAO;AAE/B,QAAI,YAAY,MAAM;AACrB,aAAO,QAAQ,OAAO,MAAM,SAAS;AAAA,IACtC;AAEA,WAAO;AAAA,EACR;AACD;;;ACbO,IAAM,UAAU,CACtB,kBACI;AACJ,SAAO,OAAO,UAAwB;AACrC,UAAM,UAAU,cAAc,KAAK;AACnC,UAAM,UAAU,QAAQ,OAAO;AAE/B,QAAI,SAAS,cAAc;AAC1B,UAAI;AAEH,cAAM,YAAY,MAAM,QAAQ,MAAM,QAAQ,QAAQ,YAAY;AAClE,cAAM,aAAa,QAAQ,QAAQ,OAAO,SAAS;AAEnD,gBAAQ,QAAQ,aAAa;AAC7B,gBAAQ,QAAQ,UAAU,UAAU;AAEpC,gBAAQ,OAAO,UAAU;AAAA,MAC1B,SAAS,GAAG;AACX,gBAAQ,MAAM,eAAe,CAAC;AAE9B,gBAAQ,QAAQ,aAAa;AAC7B,gBAAQ,OAAO,UAAU;AAEzB,gBAAQ,OAAO,MAAM,SAAS;AAAA,MAC/B;AAAA,IACD;AAAA,EACD;AACD;;;AC3BO,IAAM,QAAQ,CACpB,kBACI;AACJ,SAAO,OAAO,UAAwB;AACrC,UAAM,UAAU,cAAc,KAAK;AACnC,YAAQ,OAAO,MAAM,SAAS;AAAA,EAC/B;AACD;;;ACPO,IAAM,SAAS,CACrB,kBACI;AACJ,SAAO,OAAO,UAAwB;AACrC,UAAM,UAAU,cAAc,KAAK;AACnC,YAAQ,OAAO,OAAO,SAAS;AAAA,EAChC;AACD;;;ACPO,IAAM,SAAS,CACrB,kBACI;AACJ,SAAO,OAAO,UAAwB;AACrC,UAAM,UAAU,cAAc,KAAK;AACnC,YAAQ,OAAO,OAAO,SAAS;AAAA,EAChC;AACD;","names":[]}
@@ -0,0 +1,2 @@
1
+ export { P as Provider, a as ProviderCallbacks, g as github, k as keycloak, p as provider } from '../index-CPpCO-Oc.js';
2
+ import '../context-C2cW461e.js';
@@ -0,0 +1,377 @@
1
+ // src/helpers/encoding.ts
2
+ var binaryToBytes = (binary) => {
3
+ const bytes = new Uint8Array(binary.length);
4
+ for (let i = 0; i < binary.length; i++) {
5
+ bytes[i] = binary.charCodeAt(i);
6
+ }
7
+ return bytes;
8
+ };
9
+ var decodeBase64 = (data) => binaryToBytes(atob(data));
10
+ var decodeBase64urlIgnorePadding = (data) => {
11
+ const base64 = data.replaceAll("-", "+").replaceAll("_", "/").replace(/=+$/, "");
12
+ return decodeBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
13
+ };
14
+
15
+ // src/helpers/jwt.ts
16
+ var decodeJWTPayload = (jwt) => {
17
+ const parts = jwt.split(".");
18
+ if (parts.length !== 3) {
19
+ throw new Error("Invalid JWT");
20
+ }
21
+ let payload;
22
+ try {
23
+ payload = JSON.parse(new TextDecoder().decode(decodeBase64urlIgnorePadding(parts[1])));
24
+ } catch {
25
+ throw new Error("Invalid JWT: invalid payload encoding");
26
+ }
27
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
28
+ throw new Error("Invalid JWT: payload is not an object");
29
+ }
30
+ return payload;
31
+ };
32
+
33
+ // src/helpers/logs.ts
34
+ var logStarter = (...rest) => console.log("\x1B[36m%s\x1B[0m", "provenance:", ...rest);
35
+
36
+ // src/helpers/referrer.ts
37
+ var sanitizeReferrer = (referrer) => {
38
+ if (!referrer) {
39
+ return null;
40
+ }
41
+ if (!referrer.startsWith("/") || referrer.startsWith("//") || referrer.startsWith("/\\")) {
42
+ return null;
43
+ }
44
+ return referrer;
45
+ };
46
+
47
+ // src/resolvers/locals.ts
48
+ var localsResolver = () => async (context, resolve, logging) => {
49
+ if (logging) logStarter("locals");
50
+ let session;
51
+ try {
52
+ session = context.session.getCookie();
53
+ } catch (e) {
54
+ console.error("provenance:", e);
55
+ context.session.deleteCookie();
56
+ session = null;
57
+ }
58
+ context.locals.session = session;
59
+ return await resolve();
60
+ };
61
+
62
+ // src/resolvers/login.ts
63
+ var loginResolver = () => async (context, resolve, logging) => {
64
+ if (context.routes.login.is) {
65
+ const session = context.locals.session;
66
+ const referrer = sanitizeReferrer(context.oauth.referrer);
67
+ if (session === null) {
68
+ if (logging) logStarter("login");
69
+ await context.oauth.redirectLogin(referrer);
70
+ } else {
71
+ return referrer ? context.routes.redirect(referrer) : context.routes.home.redirect();
72
+ }
73
+ }
74
+ return await resolve();
75
+ };
76
+
77
+ // src/resolvers/logout.ts
78
+ var logoutResolver = () => async (context, resolve, logging) => {
79
+ if (context.routes.logout.is) {
80
+ const session = context.locals.session;
81
+ const referrer = sanitizeReferrer(context.oauth.referrer);
82
+ if (session !== null) {
83
+ if (logging) logStarter("logout");
84
+ context.session.deleteCookie();
85
+ context.locals.session = null;
86
+ }
87
+ return referrer ? context.routes.redirect(referrer) : context.routes.home.redirect();
88
+ }
89
+ return await resolve();
90
+ };
91
+
92
+ // src/resolvers/redirect-uri.ts
93
+ var redirectUriResolver = () => async (context, resolve, logging) => {
94
+ if (context.routes.redirectUri.is) {
95
+ if (logging) logStarter("redirectUri");
96
+ let referrer = void 0;
97
+ try {
98
+ const expectedState = context.checks.state.use();
99
+ const codeVerifier = context.checks.pkce.use();
100
+ const { code, state } = context.oauth.processAuthResponse(expectedState);
101
+ referrer = sanitizeReferrer(state.referrer) ?? void 0;
102
+ const tokens = await context.oauth.requestToken(codeVerifier, code);
103
+ const session = context.session.create(tokens);
104
+ context.session.deleteCookie();
105
+ context.session.setCookie(session);
106
+ } catch (e) {
107
+ console.error("provenance:", e);
108
+ }
109
+ if (referrer) {
110
+ context.routes.redirect(referrer);
111
+ } else {
112
+ context.routes.home.redirect();
113
+ }
114
+ }
115
+ return await resolve();
116
+ };
117
+
118
+ // src/resolvers/refresh.ts
119
+ var refreshResolver = (options) => {
120
+ return async (context, resolve, logging) => {
121
+ const session = context.locals.session;
122
+ if (session !== null && Date.now() >= session.accessExpiresAt - options.eagerRefresh) {
123
+ if (logging) logStarter("refresh");
124
+ const destroySession = () => {
125
+ context.session.deleteCookie();
126
+ context.locals.session = null;
127
+ };
128
+ if (session.refreshExpiresAt === void 0 || Date.now() < session.refreshExpiresAt) {
129
+ try {
130
+ const newTokens = await context.oauth.refresh(session.refreshToken);
131
+ const newSession = context.session.create(newTokens);
132
+ destroySession();
133
+ context.session.setCookie(newSession);
134
+ context.locals.session = newSession;
135
+ } catch (e) {
136
+ console.error("provenance:", e);
137
+ destroySession();
138
+ }
139
+ } else {
140
+ destroySession();
141
+ }
142
+ }
143
+ return await resolve();
144
+ };
145
+ };
146
+
147
+ // src/resolvers/signup.ts
148
+ var signupResolver = () => async (context, resolve, logging) => {
149
+ if (context.routes.signup.is) {
150
+ const session = context.locals.session;
151
+ const referrer = sanitizeReferrer(context.oauth.referrer);
152
+ if (session === null) {
153
+ if (logging) logStarter("signup");
154
+ await context.oauth.redirectSignup(referrer);
155
+ } else {
156
+ return referrer ? context.routes.redirect(referrer) : context.routes.home.redirect();
157
+ }
158
+ }
159
+ return await resolve();
160
+ };
161
+
162
+ // src/providers/provider.ts
163
+ var DEFAULT_SESSION_COOKIE_AGE = 60 * 10;
164
+ var provider = (authServer, endpoints, session, resolvers) => {
165
+ return {
166
+ authServer,
167
+ endpoints,
168
+ session: {
169
+ transformTokens: session.transformTokens,
170
+ validateSession: session.validateSession || ((session2) => session2),
171
+ sessionCookieAge: session.sessionCookieAge || (() => DEFAULT_SESSION_COOKIE_AGE),
172
+ fixSession: session.fixSession || ((session2) => session2)
173
+ },
174
+ resolvers: resolvers || [
175
+ redirectUriResolver(),
176
+ localsResolver(),
177
+ loginResolver(),
178
+ signupResolver(),
179
+ logoutResolver()
180
+ ]
181
+ };
182
+ };
183
+
184
+ // src/providers/github.ts
185
+ var issuer = "https://github.com/login/oauth/";
186
+ var github = (configuration, callbacks) => {
187
+ const authServer = {
188
+ issuer,
189
+ clientId: configuration.clientId,
190
+ clientSecret: configuration.clientSecret,
191
+ openid: false
192
+ };
193
+ const endpoints = {
194
+ createLoginUrl(redirectUri, checks) {
195
+ const url = new URL(`authorize`, issuer);
196
+ url.searchParams.append("client_id", configuration.clientId);
197
+ url.searchParams.append("redirect_uri", redirectUri);
198
+ url.searchParams.append("state", checks.state);
199
+ url.searchParams.append("allow_signup", "false");
200
+ url.searchParams.append("scope", configuration.scopes.join(" "));
201
+ return url;
202
+ },
203
+ createSignupUrl(redirectUri, checks) {
204
+ const url = new URL(`authorize`, issuer);
205
+ url.searchParams.append("client_id", configuration.clientId);
206
+ url.searchParams.append("redirect_uri", redirectUri);
207
+ url.searchParams.append("state", checks.state);
208
+ url.searchParams.append("allow_signup", "true");
209
+ url.searchParams.append("scope", configuration.scopes.join(" "));
210
+ return url;
211
+ },
212
+ createLogoutUrl(_session) {
213
+ return { url: new URL(`logout`, issuer) };
214
+ },
215
+ createTokenUrl() {
216
+ return new URL(`access_token`, issuer);
217
+ },
218
+ createUserinfoUrl() {
219
+ return new URL(`userinfo`, issuer);
220
+ }
221
+ };
222
+ const session = {
223
+ transformTokens: (tokens) => {
224
+ return {
225
+ accessToken: tokens.accessToken()
226
+ };
227
+ },
228
+ validateSession: (session2) => {
229
+ if (typeof session2.accessToken !== "string") {
230
+ throw new Error("Session accessToken is not valid");
231
+ }
232
+ return session2;
233
+ },
234
+ fixSession: callbacks?.fixSession,
235
+ sessionCookieAge: callbacks?.sessionCookieAge
236
+ };
237
+ return provider(authServer, endpoints, session);
238
+ };
239
+
240
+ // src/providers/keycloak.ts
241
+ var keycloak = (configuration, callbacks) => {
242
+ const defaultedOptions = {
243
+ eagerRefresh: 0,
244
+ ...configuration.options
245
+ };
246
+ const authServer = {
247
+ issuer: new URL(`/realms/${configuration.realm}`, configuration.base).toString(),
248
+ clientId: configuration.clientId,
249
+ clientSecret: configuration.clientSecret,
250
+ openid: true
251
+ };
252
+ const endpoints = {
253
+ createLoginUrl(redirectUri, checks) {
254
+ const url = new URL(
255
+ `/realms/${configuration.realm}/protocol/openid-connect/auth`,
256
+ configuration.base
257
+ );
258
+ url.searchParams.append("client_id", configuration.clientId);
259
+ url.searchParams.append("redirect_uri", redirectUri);
260
+ url.searchParams.append("response_type", "code");
261
+ url.searchParams.append("response_mode", "query");
262
+ url.searchParams.append("state", checks.state);
263
+ url.searchParams.append("code_challenge", checks.codeChallenge);
264
+ url.searchParams.append("code_challenge_method", "S256");
265
+ url.searchParams.append("scope", configuration.scopes.join(" "));
266
+ return url;
267
+ },
268
+ createSignupUrl(redirectUri, checks) {
269
+ const url = new URL(
270
+ `/realms/${configuration.realm}/protocol/openid-connect/registrations`,
271
+ configuration.base
272
+ );
273
+ url.searchParams.append("client_id", configuration.clientId);
274
+ url.searchParams.append("redirect_uri", redirectUri);
275
+ url.searchParams.append("response_type", "code");
276
+ url.searchParams.append("response_mode", "query");
277
+ url.searchParams.append("state", checks.state);
278
+ url.searchParams.append("code_challenge", checks.codeChallenge);
279
+ url.searchParams.append("code_challenge_method", "S256");
280
+ url.searchParams.append("scope", configuration.scopes.join(" "));
281
+ return url;
282
+ },
283
+ createLogoutUrl: (session2) => {
284
+ const id_token_hint = session2.idToken;
285
+ return {
286
+ url: new URL(
287
+ `/realms/${configuration.realm}/protocol/openid-connect/logout`,
288
+ configuration.base
289
+ ),
290
+ body: new URLSearchParams({
291
+ id_token_hint
292
+ })
293
+ };
294
+ },
295
+ createTokenUrl: () => new URL(`/realms/${configuration.realm}/protocol/openid-connect/token`, configuration.base),
296
+ createUserinfoUrl: () => new URL(`/realms/${configuration.realm}/protocol/openid-connect/userinfo`, configuration.base)
297
+ };
298
+ const session = {
299
+ transformTokens: (tokens) => {
300
+ const { id_token, refresh_expires_in } = tokens.body;
301
+ if (!id_token) {
302
+ throw new Error(`tokens response does not include 'id_token'`);
303
+ }
304
+ if (!tokens.accessTokenExpiresAt()) {
305
+ throw new Error(`tokens response does not include 'expires_in'`);
306
+ }
307
+ if (!tokens.refreshToken()) {
308
+ throw new Error(`tokens response does not include 'refresh_token'`);
309
+ }
310
+ if (!refresh_expires_in) {
311
+ throw new Error(`tokens response does not include 'refresh_expires_in'`);
312
+ }
313
+ const refreshExpiresInSeconds = parseInt(refresh_expires_in);
314
+ return {
315
+ idToken: id_token,
316
+ accessToken: tokens.accessToken(),
317
+ refreshToken: tokens.refreshToken(),
318
+ refreshExpiresAt: Date.now() + refreshExpiresInSeconds * 1e3,
319
+ accessExpiresAt: tokens.accessTokenExpiresAt().getTime(),
320
+ tokenType: tokens.tokenType()
321
+ };
322
+ },
323
+ validateSession: (session2) => {
324
+ if (typeof session2.idToken !== "string") {
325
+ throw new Error("Session idToken is not valid");
326
+ }
327
+ if (typeof session2.accessToken !== "string") {
328
+ throw new Error("Session accessToken is not valid");
329
+ }
330
+ if (typeof session2.refreshToken !== "string") {
331
+ throw new Error("Session refreshToken is not valid");
332
+ }
333
+ if (decodeJWTPayload(session2.idToken).iss !== authServer.issuer) {
334
+ throw new Error("Session idToken issuer mismatch");
335
+ }
336
+ if (decodeJWTPayload(session2.accessToken).iss !== authServer.issuer) {
337
+ throw new Error("Session accessToken issuer mismatch");
338
+ }
339
+ if (decodeJWTPayload(session2.refreshToken).iss !== authServer.issuer) {
340
+ throw new Error("Session refreshToken issuer mismatch");
341
+ }
342
+ if (typeof session2.accessExpiresAt !== "number" || session2.accessExpiresAt < 0) {
343
+ throw new Error("Session accessExpiresAt is not valid");
344
+ }
345
+ if (typeof session2.refreshExpiresAt !== "number" || session2.refreshExpiresAt < 0) {
346
+ throw new Error("Session refreshExpiresAt is not valid");
347
+ }
348
+ if (session2.tokenType !== "Bearer") {
349
+ throw new Error('Session tokenType is not "Bearer" type');
350
+ }
351
+ return session2;
352
+ },
353
+ fixSession: callbacks?.fixSession,
354
+ sessionCookieAge: callbacks?.sessionCookieAge || ((session2) => Math.floor((session2.refreshExpiresAt - Date.now()) / 1e3))
355
+ };
356
+ const resolvers = [
357
+ redirectUriResolver(),
358
+ localsResolver(),
359
+ loginResolver(),
360
+ signupResolver(),
361
+ async (context, resolve) => {
362
+ if (context.routes.logout.is && context.locals.session) {
363
+ await context.oauth.preLogout(context.locals.session);
364
+ }
365
+ return await resolve();
366
+ },
367
+ logoutResolver(),
368
+ refreshResolver({ eagerRefresh: defaultedOptions.eagerRefresh })
369
+ ];
370
+ return provider(authServer, endpoints, session, resolvers);
371
+ };
372
+ export {
373
+ github,
374
+ keycloak,
375
+ provider
376
+ };
377
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/helpers/encoding.ts","../../src/helpers/jwt.ts","../../src/helpers/logs.ts","../../src/helpers/referrer.ts","../../src/resolvers/locals.ts","../../src/resolvers/login.ts","../../src/resolvers/logout.ts","../../src/resolvers/redirect-uri.ts","../../src/resolvers/refresh.ts","../../src/resolvers/signup.ts","../../src/providers/provider.ts","../../src/providers/github.ts","../../src/providers/keycloak.ts"],"sourcesContent":["const bytesToBinary = (bytes: Uint8Array) => {\n\tlet binary = '';\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]);\n\t}\n\treturn binary;\n};\n\nconst binaryToBytes = (binary: string) => {\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n};\n\nexport const encodeBase64 = (bytes: Uint8Array) => btoa(bytesToBinary(bytes));\n\nexport const decodeBase64 = (data: string) => binaryToBytes(atob(data));\n\nexport const encodeBase64urlNoPadding = (bytes: Uint8Array) =>\n\tencodeBase64(bytes).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');\n\nexport const decodeBase64urlIgnorePadding = (data: string) => {\n\tconst base64 = data.replaceAll('-', '+').replaceAll('_', '/').replace(/=+$/, '');\n\treturn decodeBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));\n};\n\nexport const b64Encode = (data: string) => {\n\tconst encoder = new TextEncoder();\n\n\treturn encodeBase64(encoder.encode(data));\n};\n\nexport const b64Decode = (data: string) => {\n\tconst decoder = new TextDecoder();\n\n\treturn decoder.decode(decodeBase64(data), {});\n};\n","import { decodeBase64urlIgnorePadding } from './encoding';\n\n/**\n * decode the payload of a JWT without verifying its signature\n * @param jwt compact serialized JWT (`header.payload.signature`)\n * @returns the JWT payload claims\n */\nexport const decodeJWTPayload = (jwt: string): Record<string, unknown> => {\n\tconst parts = jwt.split('.');\n\tif (parts.length !== 3) {\n\t\tthrow new Error('Invalid JWT');\n\t}\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = JSON.parse(new TextDecoder().decode(decodeBase64urlIgnorePadding(parts[1])));\n\t} catch {\n\t\tthrow new Error('Invalid JWT: invalid payload encoding');\n\t}\n\n\tif (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {\n\t\tthrow new Error('Invalid JWT: payload is not an object');\n\t}\n\n\treturn payload as Record<string, unknown>;\n};\n","export const logStarter = (...rest: string[]) =>\n\tconsole.log('\\x1b[36m%s\\x1b[0m', 'provenance:', ...rest);\n","/**\n * only allow same-origin relative paths as redirect targets, guarding against\n * open redirects like `/login?referrer=https://evil.com` (or protocol-relative\n * `//evil.com` and backslash `/\\evil.com` variants)\n * @param referrer untrusted referrer (from url search params or oauth state)\n * @returns the referrer if it is a safe relative path, otherwise null\n */\nexport const sanitizeReferrer = (referrer: string | null | undefined): string | null => {\n\tif (!referrer) {\n\t\treturn null;\n\t}\n\n\tif (!referrer.startsWith('/') || referrer.startsWith('//') || referrer.startsWith('/\\\\')) {\n\t\treturn null;\n\t}\n\n\treturn referrer;\n};\n","import { logStarter } from '../helpers';\nimport type { Resolver } from './types';\n\nexport const localsResolver =\n\t<Session>(): Resolver<Session> =>\n\tasync (context, resolve, logging) => {\n\t\tif (logging) logStarter('locals');\n\n\t\tlet session: Session | null;\n\t\ttry {\n\t\t\tsession = context.session.getCookie();\n\t\t} catch (e) {\n\t\t\t// possibly need to redirect to an auth error page\n\t\t\tconsole.error('provenance:', e);\n\n\t\t\tcontext.session.deleteCookie();\n\t\t\tsession = null;\n\t\t}\n\n\t\tcontext.locals.session = session;\n\t\treturn await resolve();\n\t};\n","import { logStarter, sanitizeReferrer } from '../helpers';\nimport type { Resolver } from './types';\n\nexport const loginResolver =\n\t<Session>(): Resolver<Session> =>\n\tasync (context, resolve, logging) => {\n\t\tif (context.routes.login.is) {\n\t\t\tconst session = context.locals.session;\n\t\t\tconst referrer = sanitizeReferrer(context.oauth.referrer);\n\n\t\t\tif (session === null) {\n\t\t\t\tif (logging) logStarter('login');\n\n\t\t\t\tawait context.oauth.redirectLogin(referrer);\n\t\t\t} else {\n\t\t\t\treturn referrer ? context.routes.redirect(referrer) : context.routes.home.redirect();\n\t\t\t}\n\t\t}\n\t\treturn await resolve();\n\t};\n","import { logStarter, sanitizeReferrer } from '../helpers';\nimport { Resolver } from './types';\n\nexport const logoutResolver =\n\t<Session>(): Resolver<Session> =>\n\tasync (context, resolve, logging) => {\n\t\tif (context.routes.logout.is) {\n\t\t\tconst session = context.locals.session;\n\t\t\tconst referrer = sanitizeReferrer(context.oauth.referrer);\n\n\t\t\tif (session !== null) {\n\t\t\t\tif (logging) logStarter('logout');\n\n\t\t\t\tcontext.session.deleteCookie();\n\t\t\t\tcontext.locals.session = null;\n\t\t\t}\n\n\t\t\treturn referrer ? context.routes.redirect(referrer) : context.routes.home.redirect();\n\t\t}\n\t\treturn await resolve();\n\t};\n","import { logStarter, sanitizeReferrer } from '../helpers';\nimport { Resolver } from './types';\n\nexport const redirectUriResolver =\n\t<Session>(): Resolver<Session> =>\n\tasync (context, resolve, logging) => {\n\t\tif (context.routes.redirectUri.is) {\n\t\t\tif (logging) logStarter('redirectUri');\n\n\t\t\tlet referrer: string | undefined = undefined;\n\t\t\ttry {\n\t\t\t\t// state stored at beginning of authorization flow (right before login redirects to auth server)\n\t\t\t\tconst expectedState = context.checks.state.use();\n\n\t\t\t\t// nonce and code verifier stored at beginning of authorization flow (right before login redirects to auth server)\n\t\t\t\tconst codeVerifier = context.checks.pkce.use();\n\n\t\t\t\t// matching state allows this route to ensure the redirect from auth server originated from this application\n\t\t\t\t// the code returned (in url params) to this redirect uri will be used to get a token set from the oauth2 token endpoint\n\t\t\t\tconst { code, state } = context.oauth.processAuthResponse(expectedState);\n\n\t\t\t\t// set referrer, if available, for redirect\n\t\t\t\treferrer = sanitizeReferrer(state.referrer) ?? undefined;\n\n\t\t\t\t// make a request for tokens using the code and the matching code verifier\n\t\t\t\t// code verifier is required by the auth server to ensure the code matches the originator of the auth request\n\t\t\t\t// nonce allows this application to ensure that the tokens returned came from the same auth server that the code grant request was initiated with, similar to state\n\t\t\t\tconst tokens = await context.oauth.requestToken(codeVerifier, code);\n\n\t\t\t\t// turn the returned tokens into the session (including user, access token, refresh token)\n\t\t\t\tconst session = context.session.create(tokens);\n\t\t\t\tcontext.session.deleteCookie();\n\t\t\t\tcontext.session.setCookie(session);\n\t\t\t} catch (e) {\n\t\t\t\t// possibly need to redirect to an auth error page\n\t\t\t\tconsole.error('provenance:', e);\n\t\t\t}\n\n\t\t\tif (referrer) {\n\t\t\t\t// redirect to the actual route that the user was trying to access\n\t\t\t\tcontext.routes.redirect(referrer);\n\t\t\t} else {\n\t\t\t\t// if any of these checks throw an error (login took longer than the cookies were available), we should try the whole process again\n\t\t\t\t// also if somehow no referrer was set in the state\n\t\t\t\tcontext.routes.home.redirect();\n\t\t\t}\n\t\t}\n\t\treturn await resolve();\n\t};\n","import { logStarter } from '../helpers';\nimport { Resolver } from './types';\n\nexport const refreshResolver = <\n\tSession extends {\n\t\trefreshToken: string;\n\t\taccessExpiresAt: number;\n\t\trefreshExpiresAt?: number;\n\t}\n>(options: {\n\t/** in ms */\n\teagerRefresh: number;\n}): Resolver<Session> => {\n\treturn async (context, resolve, logging) => {\n\t\tconst session = context.locals.session;\n\n\t\t// attempt refresh if there is a session and the access token expires within the \"eagerRefresh\" threshold\n\t\tif (session !== null && Date.now() >= session.accessExpiresAt - options.eagerRefresh) {\n\t\t\tif (logging) logStarter('refresh');\n\n\t\t\tconst destroySession = () => {\n\t\t\t\tcontext.session.deleteCookie();\n\t\t\t\tcontext.locals.session = null;\n\t\t\t};\n\n\t\t\t// don't bother trying to refresh if your session has an expired refresh token\n\t\t\t// in practice, this should be true as the session cookie should expire at the same time as the refresh token\n\t\t\tif (session.refreshExpiresAt === undefined || Date.now() < session.refreshExpiresAt) {\n\t\t\t\ttry {\n\t\t\t\t\t// the access token has expired, so refresh with the refresh token\n\t\t\t\t\tconst newTokens = await context.oauth.refresh(session.refreshToken);\n\t\t\t\t\tconst newSession = context.session.create(newTokens);\n\n\t\t\t\t\tdestroySession();\n\t\t\t\t\tcontext.session.setCookie(newSession);\n\t\t\t\t\tcontext.locals.session = newSession;\n\t\t\t\t} catch (e) {\n\t\t\t\t\t// destroy the session if an error occurs (likely in the oauth refresh flow)\n\t\t\t\t\tconsole.error('provenance:', e);\n\t\t\t\t\tdestroySession();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdestroySession();\n\t\t\t}\n\t\t}\n\t\treturn await resolve();\n\t};\n};\n","import { logStarter, sanitizeReferrer } from '../helpers';\nimport type { Resolver } from './types';\n\nexport const signupResolver =\n\t<Session>(): Resolver<Session> =>\n\tasync (context, resolve, logging) => {\n\t\tif (context.routes.signup.is) {\n\t\t\tconst session = context.locals.session;\n\t\t\tconst referrer = sanitizeReferrer(context.oauth.referrer);\n\n\t\t\tif (session === null) {\n\t\t\t\tif (logging) logStarter('signup');\n\n\t\t\t\tawait context.oauth.redirectSignup(referrer);\n\t\t\t} else {\n\t\t\t\treturn referrer ? context.routes.redirect(referrer) : context.routes.home.redirect();\n\t\t\t}\n\t\t}\n\t\treturn await resolve();\n\t};\n","import {\n\ttype Resolver,\n\tlocalsResolver,\n\tloginResolver,\n\tlogoutResolver,\n\tredirectUriResolver,\n\tsignupResolver\n} from '../resolvers';\n\nimport {\n\tAuthServerConfiguration,\n\tEndpointsConfiguration,\n\tProvider,\n\tSessionConfiguration\n} from './types';\n\nconst DEFAULT_SESSION_COOKIE_AGE = 60 * 10;\n\n/**\n * create a generic provider with auth server configuration, endpoints, and session definition\n *\n * see [github](github.ts) and [keycloak](keycloak.ts) for examples\n */\nexport const provider = <Session extends object>(\n\tauthServer: AuthServerConfiguration,\n\tendpoints: EndpointsConfiguration<Session>,\n\tsession: Pick<SessionConfiguration<Session>, 'transformTokens'> &\n\t\tPartial<SessionConfiguration<Session>>,\n\tresolvers?: Resolver<Session>[]\n): Provider<Session> => {\n\treturn {\n\t\tauthServer,\n\t\tendpoints,\n\t\tsession: {\n\t\t\ttransformTokens: session.transformTokens,\n\t\t\tvalidateSession: session.validateSession || ((session) => session),\n\t\t\tsessionCookieAge: session.sessionCookieAge || (() => DEFAULT_SESSION_COOKIE_AGE),\n\t\t\tfixSession: session.fixSession || ((session) => session)\n\t\t},\n\t\tresolvers: resolvers || [\n\t\t\tredirectUriResolver(),\n\t\t\tlocalsResolver(),\n\t\t\tloginResolver(),\n\t\t\tsignupResolver(),\n\t\t\tlogoutResolver()\n\t\t]\n\t};\n};\n","import type { TokenRequestResult } from '../tokens';\n\nimport type { CreateProvider, EndpointsConfiguration } from './types';\nimport { provider } from './provider';\n\n/**\n * find configuration in github > developer settings > OAuth Apps > application\n */\nexport type GithubConfiguration = {\n\tclientId: string;\n\tclientSecret: string;\n\tscopes: string[];\n};\n\ntype GithubSession = {\n\taccessToken: string;\n};\n\nconst issuer = 'https://github.com/login/oauth/';\n\nexport const github: CreateProvider<GithubConfiguration, GithubSession> = (\n\tconfiguration,\n\tcallbacks\n) => {\n\tconst authServer = {\n\t\tissuer: issuer,\n\t\tclientId: configuration.clientId,\n\t\tclientSecret: configuration.clientSecret,\n\t\topenid: false\n\t};\n\tconst endpoints: EndpointsConfiguration<GithubSession> = {\n\t\tcreateLoginUrl(redirectUri, checks) {\n\t\t\tconst url = new URL(`authorize`, issuer);\n\t\t\turl.searchParams.append('client_id', configuration.clientId);\n\t\t\turl.searchParams.append('redirect_uri', redirectUri);\n\t\t\turl.searchParams.append('state', checks.state);\n\t\t\turl.searchParams.append('allow_signup', 'false');\n\t\t\turl.searchParams.append('scope', configuration.scopes.join(' '));\n\n\t\t\treturn url;\n\t\t},\n\t\tcreateSignupUrl(redirectUri, checks) {\n\t\t\tconst url = new URL(`authorize`, issuer);\n\t\t\turl.searchParams.append('client_id', configuration.clientId);\n\t\t\turl.searchParams.append('redirect_uri', redirectUri);\n\t\t\turl.searchParams.append('state', checks.state);\n\t\t\turl.searchParams.append('allow_signup', 'true');\n\t\t\turl.searchParams.append('scope', configuration.scopes.join(' '));\n\n\t\t\treturn url;\n\t\t},\n\t\tcreateLogoutUrl(_session) {\n\t\t\treturn { url: new URL(`logout`, issuer) };\n\t\t},\n\t\tcreateTokenUrl() {\n\t\t\treturn new URL(`access_token`, issuer);\n\t\t},\n\t\tcreateUserinfoUrl() {\n\t\t\treturn new URL(`userinfo`, issuer);\n\t\t}\n\t};\n\n\tconst session = {\n\t\ttransformTokens: (tokens: TokenRequestResult) => {\n\t\t\treturn {\n\t\t\t\taccessToken: tokens.accessToken()\n\t\t\t};\n\t\t},\n\t\tvalidateSession: <T extends GithubSession>(session: T) => {\n\t\t\tif (typeof session.accessToken !== 'string') {\n\t\t\t\tthrow new Error('Session accessToken is not valid');\n\t\t\t}\n\t\t\treturn session;\n\t\t},\n\t\tfixSession: callbacks?.fixSession,\n\t\tsessionCookieAge: callbacks?.sessionCookieAge\n\t};\n\n\treturn provider(authServer, endpoints, session);\n};\n","import type { TokenRequestResult } from '../tokens';\n\nimport {\n\ttype Resolver,\n\tlocalsResolver,\n\tloginResolver,\n\tlogoutResolver,\n\tredirectUriResolver,\n\trefreshResolver,\n\tsignupResolver\n} from '../resolvers';\n\nimport { decodeJWTPayload } from '../helpers/jwt';\n\nimport { provider } from './provider';\nimport type { CreateProvider, EndpointsConfiguration } from './types';\n\nexport type KeycloakConfiguration = {\n\t/** like https://auth-server.com/ (don't include \"realms\", \"protocol\", etc.) */\n\tbase: string;\n\t/** realm corresponding to users of this application (multi-tenant is currently not supported) */\n\trealm: string;\n\t/** client id corresponding to this application */\n\tclientId: string;\n\t/** client secret corresponding to this application (can be any empty string if not being used for the client) */\n\tclientSecret: string;\n\toptions: {\n\t\t/**\n\t\t * a ms adjustment to access token expiry, based upon which a server request will trigger or not trigger a token set refresh\n\t\t *\n\t\t * at 0 (default), refresh only access tokens that have expired\n\t\t *\n\t\t * at infinity, refresh on every request\n\t\t */\n\t\teagerRefresh?: number;\n\t};\n\tscopes: string[];\n};\n\ntype KeycloakSession = {\n\taccessToken: string;\n\trefreshToken: string;\n\tidToken: string;\n\trefreshExpiresAt: number;\n\taccessExpiresAt: number;\n\ttokenType: string;\n};\n\nexport const keycloak: CreateProvider<KeycloakConfiguration, KeycloakSession> = (\n\tconfiguration,\n\tcallbacks\n) => {\n\tconst defaultedOptions = {\n\t\teagerRefresh: 0,\n\t\t...configuration.options\n\t};\n\n\tconst authServer = {\n\t\tissuer: new URL(`/realms/${configuration.realm}`, configuration.base).toString(),\n\t\tclientId: configuration.clientId,\n\t\tclientSecret: configuration.clientSecret,\n\t\topenid: true\n\t};\n\tconst endpoints: EndpointsConfiguration<KeycloakSession> = {\n\t\tcreateLoginUrl(redirectUri, checks) {\n\t\t\tconst url = new URL(\n\t\t\t\t`/realms/${configuration.realm}/protocol/openid-connect/auth`,\n\t\t\t\tconfiguration.base\n\t\t\t);\n\t\t\turl.searchParams.append('client_id', configuration.clientId);\n\t\t\turl.searchParams.append('redirect_uri', redirectUri);\n\t\t\turl.searchParams.append('response_type', 'code');\n\t\t\turl.searchParams.append('response_mode', 'query');\n\t\t\turl.searchParams.append('state', checks.state);\n\t\t\turl.searchParams.append('code_challenge', checks.codeChallenge);\n\t\t\turl.searchParams.append('code_challenge_method', 'S256');\n\t\t\turl.searchParams.append('scope', configuration.scopes.join(' '));\n\n\t\t\treturn url;\n\t\t},\n\t\tcreateSignupUrl(redirectUri, checks) {\n\t\t\tconst url = new URL(\n\t\t\t\t`/realms/${configuration.realm}/protocol/openid-connect/registrations`,\n\t\t\t\tconfiguration.base\n\t\t\t);\n\t\t\turl.searchParams.append('client_id', configuration.clientId);\n\t\t\turl.searchParams.append('redirect_uri', redirectUri);\n\t\t\turl.searchParams.append('response_type', 'code');\n\t\t\turl.searchParams.append('response_mode', 'query');\n\t\t\turl.searchParams.append('state', checks.state);\n\t\t\turl.searchParams.append('code_challenge', checks.codeChallenge);\n\t\t\turl.searchParams.append('code_challenge_method', 'S256');\n\t\t\turl.searchParams.append('scope', configuration.scopes.join(' '));\n\n\t\t\treturn url;\n\t\t},\n\t\tcreateLogoutUrl: (session) => {\n\t\t\tconst id_token_hint = session.idToken;\n\t\t\treturn {\n\t\t\t\turl: new URL(\n\t\t\t\t\t`/realms/${configuration.realm}/protocol/openid-connect/logout`,\n\t\t\t\t\tconfiguration.base\n\t\t\t\t),\n\t\t\t\tbody: new URLSearchParams({\n\t\t\t\t\tid_token_hint\n\t\t\t\t})\n\t\t\t};\n\t\t},\n\t\tcreateTokenUrl: () =>\n\t\t\tnew URL(`/realms/${configuration.realm}/protocol/openid-connect/token`, configuration.base),\n\t\tcreateUserinfoUrl: () =>\n\t\t\tnew URL(`/realms/${configuration.realm}/protocol/openid-connect/userinfo`, configuration.base)\n\t};\n\n\tconst session = {\n\t\ttransformTokens: (tokens: TokenRequestResult) => {\n\t\t\tconst { id_token, refresh_expires_in } = tokens.body as {\n\t\t\t\tid_token: string;\n\t\t\t\trefresh_expires_in: string;\n\t\t\t};\n\n\t\t\tif (!id_token) {\n\t\t\t\tthrow new Error(`tokens response does not include 'id_token'`);\n\t\t\t}\n\n\t\t\tif (!tokens.accessTokenExpiresAt()) {\n\t\t\t\tthrow new Error(`tokens response does not include 'expires_in'`);\n\t\t\t}\n\n\t\t\tif (!tokens.refreshToken()) {\n\t\t\t\tthrow new Error(`tokens response does not include 'refresh_token'`);\n\t\t\t}\n\n\t\t\tif (!refresh_expires_in) {\n\t\t\t\tthrow new Error(`tokens response does not include 'refresh_expires_in'`);\n\t\t\t}\n\n\t\t\tconst refreshExpiresInSeconds = parseInt(refresh_expires_in);\n\n\t\t\treturn {\n\t\t\t\tidToken: id_token,\n\t\t\t\taccessToken: tokens.accessToken(),\n\t\t\t\trefreshToken: tokens.refreshToken(),\n\t\t\t\trefreshExpiresAt: Date.now() + refreshExpiresInSeconds * 1000,\n\t\t\t\taccessExpiresAt: tokens.accessTokenExpiresAt().getTime(),\n\t\t\t\ttokenType: tokens.tokenType()\n\t\t\t};\n\t\t},\n\t\tvalidateSession: <T extends KeycloakSession>(session: T) => {\n\t\t\tif (typeof session.idToken !== 'string') {\n\t\t\t\tthrow new Error('Session idToken is not valid');\n\t\t\t}\n\t\t\tif (typeof session.accessToken !== 'string') {\n\t\t\t\tthrow new Error('Session accessToken is not valid');\n\t\t\t}\n\t\t\tif (typeof session.refreshToken !== 'string') {\n\t\t\t\tthrow new Error('Session refreshToken is not valid');\n\t\t\t}\n\n\t\t\tif (decodeJWTPayload(session.idToken).iss !== authServer.issuer) {\n\t\t\t\tthrow new Error('Session idToken issuer mismatch');\n\t\t\t}\n\t\t\tif (decodeJWTPayload(session.accessToken).iss !== authServer.issuer) {\n\t\t\t\tthrow new Error('Session accessToken issuer mismatch');\n\t\t\t}\n\t\t\tif (decodeJWTPayload(session.refreshToken).iss !== authServer.issuer) {\n\t\t\t\tthrow new Error('Session refreshToken issuer mismatch');\n\t\t\t}\n\n\t\t\tif (typeof session.accessExpiresAt !== 'number' || session.accessExpiresAt < 0) {\n\t\t\t\tthrow new Error('Session accessExpiresAt is not valid');\n\t\t\t}\n\t\t\tif (typeof session.refreshExpiresAt !== 'number' || session.refreshExpiresAt < 0) {\n\t\t\t\tthrow new Error('Session refreshExpiresAt is not valid');\n\t\t\t}\n\t\t\tif (session.tokenType !== 'Bearer') {\n\t\t\t\tthrow new Error('Session tokenType is not \"Bearer\" type');\n\t\t\t}\n\n\t\t\treturn session;\n\t\t},\n\t\tfixSession: callbacks?.fixSession,\n\t\tsessionCookieAge:\n\t\t\tcallbacks?.sessionCookieAge ||\n\t\t\t((session) => Math.floor((session.refreshExpiresAt - Date.now()) / 1000))\n\t};\n\n\tconst resolvers: Resolver<KeycloakSession>[] = [\n\t\tredirectUriResolver(),\n\t\tlocalsResolver(),\n\t\tloginResolver(),\n\t\tsignupResolver(),\n\t\tasync (context, resolve) => {\n\t\t\tif (context.routes.logout.is && context.locals.session) {\n\t\t\t\tawait context.oauth.preLogout(context.locals.session);\n\t\t\t}\n\t\t\treturn await resolve();\n\t\t},\n\t\tlogoutResolver(),\n\t\trefreshResolver({ eagerRefresh: defaultedOptions.eagerRefresh })\n\t];\n\n\treturn provider(authServer, endpoints, session, resolvers);\n};\n"],"mappings":"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","names":["session","session","session"]}
@@ -0,0 +1,87 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ declare const frameworks: readonly ["sveltekit"];
4
+ type Framework = (typeof frameworks)[number];
5
+ type BaseOptions = {
6
+ /**
7
+ * run any command after an update of some routes.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * 'npm exec prettier ./src/lib/server/PROVENANCE.ts -- -w'
12
+ * ```
13
+ */
14
+ postUpdateRun: string | undefined;
15
+ dir: string;
16
+ filename: string;
17
+ framework: Framework;
18
+ };
19
+ type TSOptions = BaseOptions & {
20
+ runtime: 'ts';
21
+ };
22
+ type JSOptions = BaseOptions & {
23
+ generateTypes: boolean;
24
+ runtime: 'js';
25
+ };
26
+ /**
27
+ * ### register this plugin in vite config
28
+ *
29
+ * `vite.config.[ts|js]`
30
+ * ```ts title="vite.config.[ts|js]"
31
+ * import { sveltekit } from '@sveltejs/kit/vite';
32
+ * import { provenance } from '@leaguebrain/provenance/vite';
33
+ * import { defineConfig } from 'vite';
34
+
35
+ * export default defineConfig({
36
+ * plugins: [provenance(), sveltekit()]
37
+ * });
38
+ * ```
39
+ *
40
+ * ### create an auth object that provides a handle hook and a function to protect routes
41
+ *
42
+ * `src/lib/server/auth.ts`
43
+ * ```ts title="src/lib/server/auth.ts"
44
+ * import { provenance } from './PROVENANCE';
45
+ * import { github } from '@leaguebrain/provenance/providers';
46
+ *
47
+ * import { GH_CLIENT_ID, GH_CLIENT_SECRET } from '$env/static/private';
48
+ *
49
+ * export const auth = provenance(
50
+ * github({ clientId: GH_CLIENT_ID, clientSecret: GH_CLIENT_SECRET })
51
+ * );
52
+ * ```
53
+ *
54
+ * ### register the handle provided by the auth object
55
+ *
56
+ * `src/hooks.server.ts`
57
+ * ```ts title="hooks.server.ts"
58
+ * import { auth } from '$lib/server/auth';
59
+ *
60
+ * export const handle = auth.handle;
61
+ * ```
62
+ *
63
+ * `handle` will programmatically create 3 routes:
64
+ * - `redirectUri` to handle redirects from the auth server as part of the oauth authorization flow
65
+ * - `login` to redirect the user to the proper oauth url when navigated
66
+ * - `logout` to destroy the oauth and cookie session when navigated
67
+ *
68
+ * other handle functionality will manage session cookies, provide session in locals, refresh an access token,
69
+ * and keep track of the last path to which the user navigated (for redirecting back to protected routes after authenticating)
70
+ *
71
+ * ### protect server routes in load functions
72
+ *
73
+ * `src/routes/protected/+page.server.ts`
74
+ * ```ts title="src/routes/protected/+page.server.ts"
75
+ * import { auth } from '$lib/server/auth';
76
+ * import { PageServerLoad } from './types'
77
+ *
78
+ * export const load: PageServerLoad = async (event) => {
79
+ * const session = await auth.protectRoute(event);
80
+ * };
81
+ * ```
82
+ *
83
+ * `auth.protectRoute` can be awaited in `+page.server.ts` load functions to send the user to the login route if a session is required
84
+ */
85
+ declare function provenance(o?: Partial<TSOptions | JSOptions>): Plugin[];
86
+
87
+ export { type JSOptions, type TSOptions, provenance };