@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.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2023 Tripleslate Software Development, LLC.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,136 @@
1
- # Temporary Holding Version
1
+ # provenance
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@leaguebrain/provenance.svg)](https://www.npmjs.com/package/@leaguebrain/provenance) [![license](https://img.shields.io/npm/l/@leaguebrain/provenance.svg)](../../LICENSE)
4
+
5
+ Server-side OAuth/OIDC implementation for SvelteKit
6
+
7
+ ## features
8
+
9
+ - Create a `handle` for programmatically defining auth functionality in server hooks
10
+ - handle requests to redirectUri, login, and logout routes
11
+ - refresh access tokens (if refresh tokens used by the provider)
12
+ - add a session object to `event.locals`
13
+ - track the referrer for redirect after login
14
+ - Provide a `protectRoute` function for redirecting user without session to login
15
+ - Expose a callback to include extra information in the stored cookie session given tokens data
16
+
17
+ ## installation
18
+
19
+ `npm i --save-dev @leaguebrain/provenance`
20
+
21
+ ## usage
22
+
23
+ See the [example project](./example/) to see the following writeup in action.
24
+
25
+ ### include vite plugin
26
+
27
+ Include `provenance` in the vite plugin pipeline. It will generate a light "runtime"
28
+ that forms the bridge between your project's version of `sveltekit` and the `provenance` core logic.
29
+
30
+ #### `vite.config.ts`
31
+
32
+ ```ts title="vite.config.ts"
33
+ import { defineConfig } from 'vite';
34
+
35
+ import { sveltekit } from '@sveltejs/kit/vite';
36
+ import { provenance } from '@leaguebrain/provenance/vite';
37
+
38
+ export default defineConfig({
39
+ // provenance first
40
+ plugins: [provenance(), sveltekit()]
41
+ });
42
+ ```
43
+
44
+ `src/lib/server/PROVENANCE.ts` will be written on vite dev server start and build and should be checked in. This output file location can be customized and optionally output in js.
45
+
46
+ ### create auth object
47
+
48
+ #### `src/lib/server/auth.ts`
49
+
50
+ ##### github
51
+
52
+ ```ts title="src/lib/server/auth.ts"
53
+ import { provenance } from './PROVENANCE';
54
+ import { github } from '@leaguebrain/provenance/providers';
55
+ import { GH_CLIENT_ID, GH_CLIENT_SECRET } from '$env/static/private';
56
+
57
+ export const auth = provenance(
58
+ github({
59
+ clientId: GH_CLIENT_ID,
60
+ clientSecret: GH_CLIENT_SECRET
61
+ })
62
+ );
63
+ ```
64
+
65
+ ##### keycloak
66
+
67
+ ```ts title="src/lib/server/auth.ts"
68
+ import { provenance } from './PROVENANCE';
69
+ import { keycloak } from '@leaguebrain/provenance/providers';
70
+ import { KC_BASE, KC_REALM, KC_CLIENT_ID, KC_CLIENT_SECRET } from '$env/static/private';
71
+
72
+ export const auth = provenance(
73
+ keycloak({
74
+ base: KC_BASE,
75
+ realm: KC_REALM,
76
+ clientId: KC_CLIENT_ID,
77
+ clientSecret: KC_CLIENT_SECRET
78
+ })
79
+ );
80
+ ```
81
+
82
+ ### match the `App` interfaces to your provider and session callback
83
+
84
+ You will not be able to access session data from locals if your `App.Session` interface does not match the `provider` and `sessionCallback` passed to `$provenance.provenance` ([see "create auth object"](#create-auth-object)).
85
+
86
+ #### `src/app.d.ts`
87
+
88
+ ```ts title="src/app.d.ts"
89
+ declare global {
90
+ namespace App {
91
+ interface Session {
92
+ accessToken: string;
93
+ }
94
+
95
+ // interface Error {}
96
+ // interface Locals {}
97
+ // interface PageData {}
98
+ // interface Platform {}
99
+ }
100
+ }
101
+
102
+ export {};
103
+ ```
104
+
105
+ Above example applies to `github` provider. Refer to the fields in the `Session` generic parameter on other providers.
106
+ Note that `Locals` does not need to be defined, `provenance` provides a default `session` field of type `App.Session | null`.
107
+ If an app defines other `Locals` fields, they can be defined here without redefining `session` on `Locals` as typescript will merge the two declarations of the interface.
108
+
109
+ ### intialize handle hook
110
+
111
+ #### `src/hooks.server.ts`
112
+
113
+ ```ts title="hooks.server.ts"
114
+ import { auth } from '$lib/server/auth';
115
+
116
+ export const handle = auth.handle;
117
+ ```
118
+
119
+ ### protect a route
120
+
121
+ #### `src/routes/protected/+page.server.ts`
122
+
123
+ ```ts title="src/routes/protected/+page.server.ts"
124
+ import { protectRoute } from '@leaguebrain/provenance/initiatives';
125
+ import { auth } from '$lib/server/auth';
126
+
127
+ export const load = async (event) => {
128
+ const session = await protectRoute(auth.createContext)(event);
129
+
130
+ return {
131
+ session
132
+ };
133
+ };
134
+ ```
135
+
136
+ Note that `handle` in `hooks.server.ts` is not invoked during client side routing (typically, navigating to a page without a `+page.server.ts` after first load). This means that protecting a route with `auth.protectRoute` and accessing (up to date) session information must be done in a `+page.server.ts` load, not a `+page.ts` load.
@@ -0,0 +1,84 @@
1
+ /**
2
+ * wraps the JSON body of an OAuth 2.0 token endpoint response (RFC 6749 section 5)
3
+ */
4
+ declare class TokenRequestResult {
5
+ body: object;
6
+ constructor(body: object);
7
+ private string;
8
+ private requireString;
9
+ hasErrorCode(): boolean;
10
+ errorCode(): string;
11
+ hasErrorDescription(): boolean;
12
+ errorDescription(): string;
13
+ hasErrorURI(): boolean;
14
+ errorURI(): string;
15
+ hasState(): boolean;
16
+ state(): string;
17
+ tokenType(): string;
18
+ accessToken(): string;
19
+ accessTokenExpiresInSeconds(): number;
20
+ accessTokenExpiresAt(): Date;
21
+ hasRefreshToken(): boolean;
22
+ refreshToken(): string;
23
+ hasScopes(): boolean;
24
+ scopes(): string[];
25
+ }
26
+
27
+ type Context<ProviderSession, AppSession> = {
28
+ oauth: {
29
+ processAuthResponse: (expectedState: string) => {
30
+ code: string;
31
+ state: {
32
+ referrer?: string;
33
+ };
34
+ };
35
+ requestToken: (codeVerifier: string, authorizationCode: string) => Promise<TokenRequestResult>;
36
+ referrer: string | null;
37
+ redirectLogin: (referrer: string | null) => Promise<void>;
38
+ redirectSignup: (referrer: string | null) => Promise<void>;
39
+ refresh: (refreshToken: string) => Promise<TokenRequestResult>;
40
+ preLogout: (session: ProviderSession) => Promise<void>;
41
+ };
42
+ checks: {
43
+ state: {
44
+ use: () => string;
45
+ };
46
+ pkce: {
47
+ use: () => string;
48
+ };
49
+ };
50
+ session: {
51
+ create: (tokens: TokenRequestResult) => ProviderSession;
52
+ getCookie: () => ProviderSession | null;
53
+ setCookie: (session: ProviderSession) => void;
54
+ deleteCookie: () => void;
55
+ };
56
+ locals: {
57
+ get session(): AppSession | null;
58
+ set session(value: AppSession | null);
59
+ };
60
+ routes: {
61
+ redirect: (location: string | URL) => never;
62
+ redirectUri: {
63
+ is: boolean;
64
+ };
65
+ login: {
66
+ redirect: () => never;
67
+ is: boolean;
68
+ };
69
+ signup: {
70
+ redirect: () => never;
71
+ is: boolean;
72
+ };
73
+ logout: {
74
+ redirect: () => never;
75
+ is: boolean;
76
+ };
77
+ home: {
78
+ redirect: () => never;
79
+ is: boolean;
80
+ };
81
+ };
82
+ };
83
+
84
+ export { type Context as C, TokenRequestResult as T };
@@ -0,0 +1,35 @@
1
+ declare const isSessionChunkCookie: (cookieName: string, sessionCookieName: string) => boolean;
2
+ declare const chunkSessionCookies: (session: any) => string[];
3
+ declare const dechunkSessionCookies: (cookies: {
4
+ name: string;
5
+ value: string;
6
+ }[], options: {
7
+ sessionCookieName: string;
8
+ }) => string | null;
9
+
10
+ declare const encodeBase64: (bytes: Uint8Array) => string;
11
+ declare const decodeBase64: (data: string) => Uint8Array<ArrayBuffer>;
12
+ declare const encodeBase64urlNoPadding: (bytes: Uint8Array) => string;
13
+ declare const decodeBase64urlIgnorePadding: (data: string) => Uint8Array<ArrayBuffer>;
14
+ declare const b64Encode: (data: string) => string;
15
+ declare const b64Decode: (data: string) => string;
16
+
17
+ /**
18
+ * decode the payload of a JWT without verifying its signature
19
+ * @param jwt compact serialized JWT (`header.payload.signature`)
20
+ * @returns the JWT payload claims
21
+ */
22
+ declare const decodeJWTPayload: (jwt: string) => Record<string, unknown>;
23
+
24
+ declare const logStarter: (...rest: string[]) => void;
25
+
26
+ /**
27
+ * only allow same-origin relative paths as redirect targets, guarding against
28
+ * open redirects like `/login?referrer=https://evil.com` (or protocol-relative
29
+ * `//evil.com` and backslash `/\evil.com` variants)
30
+ * @param referrer untrusted referrer (from url search params or oauth state)
31
+ * @returns the referrer if it is a safe relative path, otherwise null
32
+ */
33
+ declare const sanitizeReferrer: (referrer: string | null | undefined) => string | null;
34
+
35
+ export { b64Decode, b64Encode, chunkSessionCookies, dechunkSessionCookies, decodeBase64, decodeBase64urlIgnorePadding, decodeJWTPayload, encodeBase64, encodeBase64urlNoPadding, isSessionChunkCookie, logStarter, sanitizeReferrer };
@@ -0,0 +1,113 @@
1
+ // src/helpers/cookies.ts
2
+ var chunkIndex = (cookieName, sessionCookieName) => {
3
+ const prefix = `${sessionCookieName}-`;
4
+ if (!cookieName.startsWith(prefix)) {
5
+ return null;
6
+ }
7
+ const suffix = cookieName.slice(prefix.length);
8
+ if (!/^\d+$/.test(suffix)) {
9
+ return null;
10
+ }
11
+ return parseInt(suffix);
12
+ };
13
+ var isSessionChunkCookie = (cookieName, sessionCookieName) => chunkIndex(cookieName, sessionCookieName) !== null;
14
+ var chunkSessionCookies = (session) => {
15
+ const maxCookieSize = 3500;
16
+ const fullCookie = JSON.stringify(session);
17
+ const chunksCount = Math.ceil(fullCookie.length / maxCookieSize);
18
+ const chunks = [...Array(chunksCount).keys()].map(
19
+ (i) => fullCookie.substring(i * maxCookieSize, (i + 1) * maxCookieSize)
20
+ );
21
+ return chunks;
22
+ };
23
+ var dechunkSessionCookies = (cookies, options) => {
24
+ const sessionChunkCookies = cookies.flatMap((cookie) => {
25
+ const index = chunkIndex(cookie.name, options.sessionCookieName);
26
+ return index !== null ? [{ value: cookie.value, index }] : [];
27
+ });
28
+ if (sessionChunkCookies.length > 0) {
29
+ const sorted = sessionChunkCookies.sort((a, b) => a.index - b.index);
30
+ const fullCookie = sorted.reduce((prev, current) => prev + current.value, "");
31
+ return fullCookie;
32
+ } else {
33
+ return null;
34
+ }
35
+ };
36
+
37
+ // src/helpers/encoding.ts
38
+ var bytesToBinary = (bytes) => {
39
+ let binary = "";
40
+ for (let i = 0; i < bytes.length; i++) {
41
+ binary += String.fromCharCode(bytes[i]);
42
+ }
43
+ return binary;
44
+ };
45
+ var binaryToBytes = (binary) => {
46
+ const bytes = new Uint8Array(binary.length);
47
+ for (let i = 0; i < binary.length; i++) {
48
+ bytes[i] = binary.charCodeAt(i);
49
+ }
50
+ return bytes;
51
+ };
52
+ var encodeBase64 = (bytes) => btoa(bytesToBinary(bytes));
53
+ var decodeBase64 = (data) => binaryToBytes(atob(data));
54
+ var encodeBase64urlNoPadding = (bytes) => encodeBase64(bytes).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
55
+ var decodeBase64urlIgnorePadding = (data) => {
56
+ const base64 = data.replaceAll("-", "+").replaceAll("_", "/").replace(/=+$/, "");
57
+ return decodeBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
58
+ };
59
+ var b64Encode = (data) => {
60
+ const encoder = new TextEncoder();
61
+ return encodeBase64(encoder.encode(data));
62
+ };
63
+ var b64Decode = (data) => {
64
+ const decoder = new TextDecoder();
65
+ return decoder.decode(decodeBase64(data), {});
66
+ };
67
+
68
+ // src/helpers/jwt.ts
69
+ var decodeJWTPayload = (jwt) => {
70
+ const parts = jwt.split(".");
71
+ if (parts.length !== 3) {
72
+ throw new Error("Invalid JWT");
73
+ }
74
+ let payload;
75
+ try {
76
+ payload = JSON.parse(new TextDecoder().decode(decodeBase64urlIgnorePadding(parts[1])));
77
+ } catch {
78
+ throw new Error("Invalid JWT: invalid payload encoding");
79
+ }
80
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
81
+ throw new Error("Invalid JWT: payload is not an object");
82
+ }
83
+ return payload;
84
+ };
85
+
86
+ // src/helpers/logs.ts
87
+ var logStarter = (...rest) => console.log("\x1B[36m%s\x1B[0m", "provenance:", ...rest);
88
+
89
+ // src/helpers/referrer.ts
90
+ var sanitizeReferrer = (referrer) => {
91
+ if (!referrer) {
92
+ return null;
93
+ }
94
+ if (!referrer.startsWith("/") || referrer.startsWith("//") || referrer.startsWith("/\\")) {
95
+ return null;
96
+ }
97
+ return referrer;
98
+ };
99
+ export {
100
+ b64Decode,
101
+ b64Encode,
102
+ chunkSessionCookies,
103
+ dechunkSessionCookies,
104
+ decodeBase64,
105
+ decodeBase64urlIgnorePadding,
106
+ decodeJWTPayload,
107
+ encodeBase64,
108
+ encodeBase64urlNoPadding,
109
+ isSessionChunkCookie,
110
+ logStarter,
111
+ sanitizeReferrer
112
+ };
113
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/helpers/cookies.ts","../../src/helpers/encoding.ts","../../src/helpers/jwt.ts","../../src/helpers/logs.ts","../../src/helpers/referrer.ts"],"sourcesContent":["const chunkIndex = (cookieName: string, sessionCookieName: string): number | null => {\n\tconst prefix = `${sessionCookieName}-`;\n\tif (!cookieName.startsWith(prefix)) {\n\t\treturn null;\n\t}\n\n\tconst suffix = cookieName.slice(prefix.length);\n\tif (!/^\\d+$/.test(suffix)) {\n\t\treturn null;\n\t}\n\n\treturn parseInt(suffix);\n};\n\nexport const isSessionChunkCookie = (cookieName: string, sessionCookieName: string): boolean =>\n\tchunkIndex(cookieName, sessionCookieName) !== null;\n\nexport const chunkSessionCookies = (session: any) => {\n\tconst maxCookieSize = 3500;\n\tconst fullCookie = JSON.stringify(session);\n\n\tconst chunksCount = Math.ceil(fullCookie.length / maxCookieSize);\n\tconst chunks = [...Array(chunksCount).keys()].map((i) =>\n\t\tfullCookie.substring(i * maxCookieSize, (i + 1) * maxCookieSize)\n\t);\n\n\treturn chunks;\n};\n\nexport const dechunkSessionCookies = (\n\tcookies: { name: string; value: string }[],\n\toptions: { sessionCookieName: string }\n) => {\n\tconst sessionChunkCookies = cookies.flatMap((cookie) => {\n\t\tconst index = chunkIndex(cookie.name, options.sessionCookieName);\n\t\treturn index !== null ? [{ value: cookie.value, index }] : [];\n\t});\n\n\tif (sessionChunkCookies.length > 0) {\n\t\tconst sorted = sessionChunkCookies.sort((a, b) => a.index - b.index);\n\n\t\tconst fullCookie = sorted.reduce((prev, current) => prev + current.value, '');\n\n\t\treturn fullCookie;\n\t} else {\n\t\treturn null;\n\t}\n};\n","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"],"mappings":"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","names":[]}
@@ -0,0 +1,103 @@
1
+ import { C as Context, T as TokenRequestResult } from './context-C2cW461e.js';
2
+
3
+ type Resolver<ProviderSession> = (context: Context<ProviderSession, ProviderSession>, resolve: () => Response | Promise<Response>, logging: boolean) => Response | Promise<Response>;
4
+
5
+ type AuthServerConfiguration = {
6
+ issuer: string;
7
+ clientId: string;
8
+ clientSecret: string;
9
+ openid: boolean;
10
+ };
11
+ type Checks = {
12
+ state: string;
13
+ codeChallenge: string;
14
+ };
15
+ type EndpointsConfiguration<Session> = {
16
+ createLoginUrl: (redirectUri: string, checks: Checks) => URL;
17
+ createSignupUrl: (redirectUri: string, checks: Checks) => URL;
18
+ createLogoutUrl: (session: Session) => {
19
+ url: URL;
20
+ body?: URLSearchParams;
21
+ };
22
+ createTokenUrl: () => URL;
23
+ createUserinfoUrl: () => URL;
24
+ };
25
+ type SessionConfiguration<Session> = {
26
+ transformTokens: (tokens: TokenRequestResult) => Session;
27
+ validateSession: <T extends Session>(session: T) => T;
28
+ sessionCookieAge: (session: Session) => number;
29
+ fixSession: (session: Session) => Session;
30
+ };
31
+ /**
32
+ * @param sessionCookieAge optional callback to set cookie age based on session (in seconds)
33
+ * @param fixSession optional callback to alter field(s) in the session after receiving from auth server
34
+ */
35
+ type ProviderCallbacks<Session> = {
36
+ sessionCookieAge?: (session: Session) => number;
37
+ fixSession?: (session: Session) => Session;
38
+ };
39
+ type Provider<Session> = {
40
+ authServer: AuthServerConfiguration;
41
+ endpoints: EndpointsConfiguration<Session>;
42
+ session: SessionConfiguration<Session>;
43
+ resolvers: Resolver<Session>[];
44
+ };
45
+ /**
46
+ * @param configuration OAuth auth server and client configuration (specific to provider)
47
+ * @param callbacks optional callbacks to customize default provider-specific behaviors (ie. alter session values, set the length of the cookie)
48
+ * @returns initialized provider to be passed to the generated runtime
49
+ */
50
+ type CreateProvider<ProviderConfiguration, Session> = (configuration: ProviderConfiguration, callbacks?: ProviderCallbacks<Session>) => Provider<Session>;
51
+
52
+ /**
53
+ * create a generic provider with auth server configuration, endpoints, and session definition
54
+ *
55
+ * see [github](github.ts) and [keycloak](keycloak.ts) for examples
56
+ */
57
+ declare const provider: <Session extends object>(authServer: AuthServerConfiguration, endpoints: EndpointsConfiguration<Session>, session: Pick<SessionConfiguration<Session>, "transformTokens"> & Partial<SessionConfiguration<Session>>, resolvers?: Resolver<Session>[]) => Provider<Session>;
58
+
59
+ /**
60
+ * find configuration in github > developer settings > OAuth Apps > application
61
+ */
62
+ type GithubConfiguration = {
63
+ clientId: string;
64
+ clientSecret: string;
65
+ scopes: string[];
66
+ };
67
+ type GithubSession = {
68
+ accessToken: string;
69
+ };
70
+ declare const github: CreateProvider<GithubConfiguration, GithubSession>;
71
+
72
+ type KeycloakConfiguration = {
73
+ /** like https://auth-server.com/ (don't include "realms", "protocol", etc.) */
74
+ base: string;
75
+ /** realm corresponding to users of this application (multi-tenant is currently not supported) */
76
+ realm: string;
77
+ /** client id corresponding to this application */
78
+ clientId: string;
79
+ /** client secret corresponding to this application (can be any empty string if not being used for the client) */
80
+ clientSecret: string;
81
+ options: {
82
+ /**
83
+ * a ms adjustment to access token expiry, based upon which a server request will trigger or not trigger a token set refresh
84
+ *
85
+ * at 0 (default), refresh only access tokens that have expired
86
+ *
87
+ * at infinity, refresh on every request
88
+ */
89
+ eagerRefresh?: number;
90
+ };
91
+ scopes: string[];
92
+ };
93
+ type KeycloakSession = {
94
+ accessToken: string;
95
+ refreshToken: string;
96
+ idToken: string;
97
+ refreshExpiresAt: number;
98
+ accessExpiresAt: number;
99
+ tokenType: string;
100
+ };
101
+ declare const keycloak: CreateProvider<KeycloakConfiguration, KeycloakSession>;
102
+
103
+ export { type Provider as P, type Resolver as R, type ProviderCallbacks as a, github as g, keycloak as k, provider as p };