@fluixi/oauth2 1.0.0-alpha.1

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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api-guard.cjs +185 -0
  3. package/dist/api-guard.d.ts +94 -0
  4. package/dist/api-guard.d.ts.map +1 -0
  5. package/dist/api-guard.js +127 -0
  6. package/dist/api-guard.mjs +141 -0
  7. package/dist/browser-FDUOEUM2.mjs +3544 -0
  8. package/dist/browser-ONMHX3Z3.mjs +3545 -0
  9. package/dist/browser.cjs +735 -0
  10. package/dist/browser.d.ts +3 -0
  11. package/dist/browser.d.ts.map +1 -0
  12. package/dist/browser.js +448 -0
  13. package/dist/browser.mjs +704 -0
  14. package/dist/callback.cjs +125 -0
  15. package/dist/callback.d.ts +113 -0
  16. package/dist/callback.d.ts.map +1 -0
  17. package/dist/callback.js +183 -0
  18. package/dist/callback.mjs +106 -0
  19. package/dist/chunk-25WE6UWI.mjs +10 -0
  20. package/dist/chunk-4NSOGPLM.mjs +15 -0
  21. package/dist/chunk-7P6ASYW6.mjs +9 -0
  22. package/dist/chunk-OGSFNTKA.mjs +13 -0
  23. package/dist/client-auth.cjs +115 -0
  24. package/dist/client-auth.d.ts +43 -0
  25. package/dist/client-auth.d.ts.map +1 -0
  26. package/dist/client-auth.js +112 -0
  27. package/dist/client-auth.mjs +84 -0
  28. package/dist/discovery.cjs +86 -0
  29. package/dist/discovery.d.ts +15 -0
  30. package/dist/discovery.d.ts.map +1 -0
  31. package/dist/discovery.js +51 -0
  32. package/dist/discovery.mjs +63 -0
  33. package/dist/dpop-LVPWWADA.mjs +80 -0
  34. package/dist/dpop-NS7NKOWM.mjs +79 -0
  35. package/dist/dpop-store.cjs +152 -0
  36. package/dist/dpop-store.d.ts +37 -0
  37. package/dist/dpop-store.d.ts.map +1 -0
  38. package/dist/dpop-store.js +119 -0
  39. package/dist/dpop-store.mjs +121 -0
  40. package/dist/dpop.cjs +117 -0
  41. package/dist/dpop.d.ts +72 -0
  42. package/dist/dpop.d.ts.map +1 -0
  43. package/dist/dpop.js +114 -0
  44. package/dist/dpop.mjs +86 -0
  45. package/dist/env-config.cjs +366 -0
  46. package/dist/env-config.d.ts +103 -0
  47. package/dist/env-config.d.ts.map +1 -0
  48. package/dist/env-config.js +184 -0
  49. package/dist/env-config.mjs +343 -0
  50. package/dist/env-credentials.cjs +82 -0
  51. package/dist/env-credentials.d.ts +68 -0
  52. package/dist/env-credentials.d.ts.map +1 -0
  53. package/dist/env-credentials.js +76 -0
  54. package/dist/env-credentials.mjs +51 -0
  55. package/dist/index.cjs +940 -0
  56. package/dist/index.d.ts +104 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +101 -0
  59. package/dist/index.mjs +909 -0
  60. package/dist/pkce.cjs +87 -0
  61. package/dist/pkce.d.ts +58 -0
  62. package/dist/pkce.d.ts.map +1 -0
  63. package/dist/pkce.js +112 -0
  64. package/dist/pkce.mjs +66 -0
  65. package/dist/presets.cjs +240 -0
  66. package/dist/presets.d.ts +140 -0
  67. package/dist/presets.d.ts.map +1 -0
  68. package/dist/presets.js +182 -0
  69. package/dist/presets.mjs +217 -0
  70. package/dist/return-to.cjs +46 -0
  71. package/dist/return-to.d.ts +49 -0
  72. package/dist/return-to.d.ts.map +1 -0
  73. package/dist/return-to.js +71 -0
  74. package/dist/return-to.mjs +25 -0
  75. package/dist/roles.cjs +56 -0
  76. package/dist/roles.d.ts +35 -0
  77. package/dist/roles.d.ts.map +1 -0
  78. package/dist/roles.js +37 -0
  79. package/dist/roles.mjs +35 -0
  80. package/dist/server-client.cjs +134 -0
  81. package/dist/server-client.d.ts +12 -0
  82. package/dist/server-client.d.ts.map +1 -0
  83. package/dist/server-client.js +107 -0
  84. package/dist/server-client.mjs +111 -0
  85. package/dist/server.cjs +470 -0
  86. package/dist/server.d.ts +89 -0
  87. package/dist/server.d.ts.map +1 -0
  88. package/dist/server.js +360 -0
  89. package/dist/server.mjs +441 -0
  90. package/dist/service-client.cjs +244 -0
  91. package/dist/service-client.d.ts +89 -0
  92. package/dist/service-client.d.ts.map +1 -0
  93. package/dist/service-client.js +112 -0
  94. package/dist/service-client.mjs +213 -0
  95. package/dist/service.cjs +903 -0
  96. package/dist/service.d.ts +96 -0
  97. package/dist/service.d.ts.map +1 -0
  98. package/dist/service.js +129 -0
  99. package/dist/service.mjs +874 -0
  100. package/dist/tokens.cjs +29 -0
  101. package/dist/tokens.d.ts +13 -0
  102. package/dist/tokens.d.ts.map +1 -0
  103. package/dist/tokens.js +21 -0
  104. package/dist/tokens.mjs +8 -0
  105. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  106. package/dist/types.cjs +33 -0
  107. package/dist/types.d.ts +239 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +23 -0
  110. package/dist/types.mjs +12 -0
  111. package/dist/verify.cjs +724 -0
  112. package/dist/verify.d.ts +205 -0
  113. package/dist/verify.d.ts.map +1 -0
  114. package/dist/verify.js +371 -0
  115. package/dist/verify.mjs +590 -0
  116. package/package.json +121 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The application-facing half.
3
+ *
4
+ * `AuthClient` knows the protocol. This knows the application: who is signed in, whether
5
+ * the session is still good, when to refresh, and where to send someone who is not
6
+ * allowed in. It is built on `@fluixi/session`, which already owns that state machine,
7
+ * rather than a second one written here.
8
+ *
9
+ * The mapping onto a session adapter is the part worth explaining. `createSession`'s
10
+ * `login(creds)` means "turn these credentials into a token". In a redirect flow the
11
+ * credential is the authorization code on the callback URL, so `login` is the code
12
+ * exchange and the redirect itself is a separate call that never returns. Everything
13
+ * else lines up directly: `fetchUser`, `refresh`, `logout` and `getExpiry`.
14
+ */
15
+ import { type AccessControl, type Session } from '@fluixi/session';
16
+ import { createAuthHooks } from '@fluixi/auth/client';
17
+ import type { AuthClient, AuthConfig, CallbackParams, LoginOptions } from './types.js';
18
+ /** The credential a redirect flow produces. */
19
+ export type AuthCredentials = CallbackParams;
20
+ export interface AuthServiceOptions<U, P = unknown, Q = (p: P) => boolean> {
21
+ /** How the flow runs and where the tokens live. */
22
+ config: AuthConfig;
23
+ /**
24
+ * The client, when it should not be built from `config`.
25
+ *
26
+ * The seam for a test, and for a provider that needs something neither built-in
27
+ * implementation does.
28
+ */
29
+ client?: AuthClient;
30
+ /**
31
+ * The user, when the provider's userinfo shape is not what the app wants.
32
+ *
33
+ * Given one, it replaces the client's own `fetchUser`, which is the usual case: an app
34
+ * has its own `/me` carrying roles the identity provider knows nothing about.
35
+ */
36
+ fetchUser?: (accessToken: string) => Promise<U>;
37
+ /** Client-side RBAC, for deciding what to render. The server still decides what is allowed. */
38
+ access?: AccessControl<U, P, Q>;
39
+ /** Passed through to the session. */
40
+ storageKeys?: {
41
+ token?: string;
42
+ user?: string;
43
+ expiry?: string;
44
+ };
45
+ refreshSkewMs?: number;
46
+ onLogin?: (user: U) => void;
47
+ onLogout?: () => void;
48
+ onExpired?: () => void;
49
+ onError?: (error: unknown, op: string) => void;
50
+ /**
51
+ * How a guard sends someone somewhere else.
52
+ *
53
+ * A factory rather than a function, because `useNavigate` has to run inside the
54
+ * component being rendered. Give it `{ handler: useNavigate }` and a guard performs a
55
+ * client-side navigation; leave it out and it assigns `location`, which reloads the
56
+ * document and discards the reactive tree.
57
+ */
58
+ navigate?: {
59
+ handler: () => (to: string, options?: unknown) => void;
60
+ };
61
+ /** Where a signed-out visitor is sent. Default `/login`. */
62
+ loginPath?: string;
63
+ /** Where a signed-in visitor is sent from a guest-only page. Default `/`. */
64
+ homePath?: string;
65
+ /** Where someone lacking a role is sent. Default `/403`. */
66
+ forbiddenPath?: string;
67
+ }
68
+ /**
69
+ * What an application injects.
70
+ *
71
+ * The session's own surface (`user`, `token`, `status`, `isAuthenticated`, `can`, …) plus
72
+ * the three things a redirect flow adds.
73
+ */
74
+ export interface AuthService<U, P = unknown, Q = (p: P) => boolean> extends Session<U, AuthCredentials, unknown, P, Q>, Pick<ReturnType<typeof createAuthHooks<U, P, Q>>, 'protectedRoute' | 'Can' | 'Protect' | 'useAuth' | 'useUser' | 'useCan'> {
75
+ /** Send the visitor to the authorization server. Does not return in the browser. */
76
+ signIn(options?: LoginOptions): Promise<void>;
77
+ /**
78
+ * Finish a login from the callback URL.
79
+ *
80
+ * Returns where the app should go next: what `signIn` recorded, or `/`.
81
+ */
82
+ complete(params?: CallbackParams): Promise<string>;
83
+ /** The protocol half, for anything this service does not wrap. */
84
+ readonly client: AuthClient;
85
+ }
86
+ /**
87
+ * The parameters on the current URL, which is what a callback route has.
88
+ *
89
+ * Empty during a server render, where there is no location to read. A callback route is
90
+ * rendered on both sides, and throwing here would fail the render before the browser ever
91
+ * got the chance to finish the exchange, which is the only place it can happen: the PKCE
92
+ * verifier is in the browser's storage.
93
+ */
94
+ export declare function paramsFromUrl(url?: string | URL): CallbackParams;
95
+ export declare function createAuthService<U, P = unknown, Q = (p: P) => boolean>(options: AuthServiceOptions<U, P, Q>): AuthService<U, P, Q>;
96
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAiB,KAAK,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAMtD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAU,MAAM,YAAY,CAAC;AAE/F,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC;AAE7C,MAAM,WAAW,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO;IACvE,mDAAmD;IACnD,MAAM,EAAE,UAAU,CAAC;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,+FAA+F;IAC/F,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,qCAAqC;IACrC,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/C;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC;IACtE,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAChE,SAAQ,OAAO,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAChD,IAAI,CACF,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAC3C,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CACxE;IACH,oFAAoF;IACpF,MAAM,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,cAAc,CAWhE;AAUD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,EACrE,OAAO,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACnC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CA0FtB"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The application-facing half.
3
+ *
4
+ * `AuthClient` knows the protocol. This knows the application: who is signed in, whether
5
+ * the session is still good, when to refresh, and where to send someone who is not
6
+ * allowed in. It is built on `@fluixi/session`, which already owns that state machine,
7
+ * rather than a second one written here.
8
+ *
9
+ * The mapping onto a session adapter is the part worth explaining. `createSession`'s
10
+ * `login(creds)` means "turn these credentials into a token". In a redirect flow the
11
+ * credential is the authorization code on the callback URL, so `login` is the code
12
+ * exchange and the redirect itself is a separate call that never returns. Everything
13
+ * else lines up directly: `fetchUser`, `refresh`, `logout` and `getExpiry`.
14
+ */
15
+ import { createSession } from '@fluixi/session';
16
+ import { createAuthHooks } from '@fluixi/auth/client';
17
+ import { expiryFromJwt } from './pkce.js';
18
+ import { safeReturnTo } from './return-to.js';
19
+ import { createBrowserClient } from './browser.js';
20
+ import { createServerClient, COOKIE_SESSION } from './server-client.js';
21
+ import { OAuthError } from './types.js';
22
+ /**
23
+ * The parameters on the current URL, which is what a callback route has.
24
+ *
25
+ * Empty during a server render, where there is no location to read. A callback route is
26
+ * rendered on both sides, and throwing here would fail the render before the browser ever
27
+ * got the chance to finish the exchange, which is the only place it can happen: the PKCE
28
+ * verifier is in the browser's storage.
29
+ */
30
+ export function paramsFromUrl(url) {
31
+ const href = url ?? (typeof location === 'undefined' ? null : location.href);
32
+ if (!href)
33
+ return {};
34
+ const { searchParams } = new URL(String(href));
35
+ return {
36
+ code: searchParams.get('code') ?? undefined,
37
+ state: searchParams.get('state') ?? undefined,
38
+ error: searchParams.get('error') ?? undefined,
39
+ errorDescription: searchParams.get('error_description') ?? undefined,
40
+ };
41
+ }
42
+ /** Only `client` and `config` matter here, so the user type is irrelevant. */
43
+ function clientFor(options) {
44
+ if (options.client)
45
+ return options.client;
46
+ return options.config.tokens === 'server'
47
+ ? createServerClient(options.config)
48
+ : createBrowserClient(options.config);
49
+ }
50
+ export function createAuthService(options) {
51
+ const client = clientFor(options);
52
+ // Held so `getExpiry` can answer for a token the session hands back, and so a
53
+ // server-mode session has something to report when the token is a cookie.
54
+ let latest = null;
55
+ const session = createSession({
56
+ /** The credential is the callback, so this is the code exchange. */
57
+ async login(params) {
58
+ latest = await client.handleCallback(params);
59
+ return { token: latest.accessToken, expiresAt: latest.expiresAt };
60
+ },
61
+ async fetchUser(token) {
62
+ if (options.fetchUser)
63
+ return options.fetchUser(token);
64
+ return client.fetchUser(token === COOKIE_SESSION ? undefined : token);
65
+ },
66
+ async refresh() {
67
+ latest = await client.refresh();
68
+ return { token: latest.accessToken, expiresAt: latest.expiresAt };
69
+ },
70
+ async logout() {
71
+ latest = null;
72
+ await client.logout();
73
+ },
74
+ getExpiry(token) {
75
+ // A cookie session has no expiry the browser can read; the server refuses when it
76
+ // is over, and the unauthorized guard handles that.
77
+ if (token === COOKIE_SESSION)
78
+ return null;
79
+ return latest?.expiresAt ?? expiryFromJwt(token);
80
+ },
81
+ access: options.access,
82
+ storageKeys: options.storageKeys,
83
+ refreshSkewMs: options.refreshSkewMs,
84
+ onLogin: options.onLogin,
85
+ onLogout: options.onLogout,
86
+ onExpired: options.onExpired,
87
+ onError: options.onError,
88
+ });
89
+ // The hooks read the session they are given, so they are part of the same object
90
+ // rather than a second file an app has to wire up and keep in step.
91
+ const hooks = createAuthHooks({
92
+ session: session,
93
+ navigate: options.navigate,
94
+ loginPath: options.loginPath ?? '/login',
95
+ homePath: options.homePath ?? '/',
96
+ forbiddenPath: options.forbiddenPath ?? '/403',
97
+ });
98
+ return Object.assign(session, {
99
+ client,
100
+ protectedRoute: hooks.protectedRoute,
101
+ Can: hooks.Can,
102
+ Protect: hooks.Protect,
103
+ useAuth: hooks.useAuth,
104
+ useUser: hooks.useUser,
105
+ useCan: hooks.useCan,
106
+ signIn(loginOptions = {}) {
107
+ return client.login(loginOptions);
108
+ },
109
+ /**
110
+ * Finish a login and say where to go.
111
+ *
112
+ * The destination is checked before it is handed back, so an app that navigates to it
113
+ * without looking cannot be turned into an open redirect by a poisoned login link.
114
+ */
115
+ async complete(params) {
116
+ const callback = params ?? paramsFromUrl();
117
+ if (!callback.code && !callback.error) {
118
+ throw new OAuthError('complete() found no callback parameters. It runs in the browser, on the redirect URI, where the code and the PKCE verifier both are.', 'no_callback_params');
119
+ }
120
+ await session.login(callback);
121
+ return safeReturnTo(client.consumeReturnTo?.(), {
122
+ fallback: '/',
123
+ allowedOrigins: 'allowedReturnOrigins' in options.config
124
+ ? options.config.allowedReturnOrigins
125
+ : undefined,
126
+ });
127
+ },
128
+ });
129
+ }