limen-auth 0.0.0 → 0.0.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.
- package/README.md +16 -41
- package/dist/broadcast-channel.mjs +51 -0
- package/dist/build-tree.mjs +73 -0
- package/dist/client.d.mts +7 -0
- package/dist/client.mjs +71 -0
- package/dist/{constants-CsR2pQ_9.mjs → constants.mjs} +1 -3
- package/dist/context.d.mts +32 -0
- package/dist/define-plugin.d.mts +40 -0
- package/dist/{define-plugin-C7WOGU4b.mjs → define-plugin.mjs} +1 -3
- package/dist/envelope.mjs +39 -0
- package/dist/errors.d.mts +19 -0
- package/dist/{errors-4YJYt6f0.mjs → errors.mjs} +1 -3
- package/dist/fetcher.d.mts +17 -0
- package/dist/fetcher.mjs +112 -0
- package/dist/{helpers-CvmKjWi2.d.mts → helpers.d.mts} +1 -2
- package/dist/{helpers-Cs3VtXdv.mjs → helpers.mjs} +1 -3
- package/dist/hooks.d.mts +1 -0
- package/dist/hooks.mjs +34 -0
- package/dist/index.d.mts +14 -30
- package/dist/index.mjs +9 -11
- package/dist/infer.d.mts +42 -0
- package/dist/json-deep-equal.mjs +27 -0
- package/dist/normalize.d.mts +12 -0
- package/dist/normalize.mjs +19 -0
- package/dist/package.mjs +4 -0
- package/dist/path.mjs +41 -0
- package/dist/pipeline.mjs +65 -0
- package/dist/plugin.d.mts +71 -0
- package/dist/plugins/bearer/index.d.mts +13 -1
- package/dist/plugins/bearer/index.mjs +43 -1
- package/dist/plugins/bearer/storage.d.mts +8 -0
- package/dist/plugins/bearer/storage.mjs +41 -0
- package/dist/{index-B8SpHkSd.d.mts → plugins/bearer/types.d.mts} +1 -18
- package/dist/plugins/credential/index.d.mts +54 -1
- package/dist/plugins/credential/index.mjs +2 -4
- package/dist/plugins/credential/types.d.mts +35 -0
- package/dist/plugins/index.d.mts +13 -6
- package/dist/plugins/index.mjs +3 -2
- package/dist/plugins/magic-link/index.d.mts +17 -1
- package/dist/plugins/magic-link/index.mjs +2 -4
- package/dist/plugins/magic-link/types.d.mts +13 -0
- package/dist/plugins/oauth/index.d.mts +47 -1
- package/dist/plugins/oauth/index.mjs +3 -5
- package/dist/plugins/oauth/types.d.mts +35 -0
- package/dist/plugins/session-jwt/index.d.mts +24 -1
- package/dist/plugins/session-jwt/index.mjs +95 -1
- package/dist/plugins/session-jwt/jwt.mjs +34 -0
- package/dist/plugins/session-jwt/types.d.mts +11 -0
- package/dist/plugins/two-factor/index.d.mts +41 -1
- package/dist/plugins/two-factor/index.mjs +2 -4
- package/dist/plugins/two-factor/types.d.mts +26 -0
- package/dist/react/index.d.mts +6 -7
- package/dist/react/index.mjs +2 -20
- package/dist/react/react-store.d.mts +6 -0
- package/dist/react/react-store.mjs +18 -0
- package/dist/route.d.mts +80 -0
- package/dist/{route-DGxvFqWl.mjs → route.mjs} +1 -3
- package/dist/routes.d.mts +53 -0
- package/dist/routes.mjs +58 -0
- package/dist/serialize.d.mts +11 -0
- package/dist/serialize.mjs +23 -0
- package/dist/session-store.d.mts +29 -0
- package/dist/session-store.mjs +79 -0
- package/dist/session-sync.mjs +47 -0
- package/dist/solid/index.d.mts +6 -7
- package/dist/solid/index.mjs +2 -17
- package/dist/solid/solid-store.d.mts +7 -0
- package/dist/solid/solid-store.mjs +15 -0
- package/dist/svelte/index.d.mts +5 -3
- package/dist/svelte/index.mjs +1 -3
- package/dist/type-utils.d.mts +15 -0
- package/dist/types.d.mts +97 -0
- package/dist/version.d.mts +4 -0
- package/dist/version.mjs +5 -0
- package/dist/vue/index.d.mts +6 -19
- package/dist/vue/index.mjs +2 -25
- package/dist/vue/vue-store.d.mts +18 -0
- package/dist/vue/vue-store.mjs +23 -0
- package/package.json +4 -3
- package/dist/bearer-Cqmrmjjf.mjs +0 -84
- package/dist/bearer-Cqmrmjjf.mjs.map +0 -1
- package/dist/client-Er91De-z.mjs +0 -705
- package/dist/client-Er91De-z.mjs.map +0 -1
- package/dist/constants-CsR2pQ_9.mjs.map +0 -1
- package/dist/define-plugin-C7WOGU4b.mjs.map +0 -1
- package/dist/define-plugin-Dv0xXIaH.d.mts +0 -450
- package/dist/define-plugin-Dv0xXIaH.d.mts.map +0 -1
- package/dist/errors-4YJYt6f0.mjs.map +0 -1
- package/dist/helpers-Cs3VtXdv.mjs.map +0 -1
- package/dist/helpers-CvmKjWi2.d.mts.map +0 -1
- package/dist/index-B8SpHkSd.d.mts.map +0 -1
- package/dist/index-C6atwjEq.d.mts +0 -65
- package/dist/index-C6atwjEq.d.mts.map +0 -1
- package/dist/index-C9EuA9UZ.d.mts +0 -32
- package/dist/index-C9EuA9UZ.d.mts.map +0 -1
- package/dist/index-Cgr2wHmM.d.mts +0 -87
- package/dist/index-Cgr2wHmM.d.mts.map +0 -1
- package/dist/index-DV6YcNSu.d.mts +0 -81
- package/dist/index-DV6YcNSu.d.mts.map +0 -1
- package/dist/index-dEksIImj.d.mts +0 -28
- package/dist/index-dEksIImj.d.mts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/plugins/credential/index.mjs.map +0 -1
- package/dist/plugins/magic-link/index.mjs.map +0 -1
- package/dist/plugins/oauth/index.mjs.map +0 -1
- package/dist/plugins/two-factor/index.mjs.map +0 -1
- package/dist/react/index.d.mts.map +0 -1
- package/dist/react/index.mjs.map +0 -1
- package/dist/route-DGxvFqWl.mjs.map +0 -1
- package/dist/session-jwt-DgLdMQxP.mjs +0 -129
- package/dist/session-jwt-DgLdMQxP.mjs.map +0 -1
- package/dist/solid/index.d.mts.map +0 -1
- package/dist/solid/index.mjs.map +0 -1
- package/dist/svelte/index.d.mts.map +0 -1
- package/dist/svelte/index.mjs.map +0 -1
- package/dist/vue/index.d.mts.map +0 -1
- package/dist/vue/index.mjs.map +0 -1
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { localStorageBearerStorage, memoryBearerStorage, resolveDefaultStorage } from "./bearer/storage.mjs";
|
|
2
|
+
import { bearerPlugin } from "./bearer/index.mjs";
|
|
2
3
|
import { credentialPasswordPlugin } from "./credential/index.mjs";
|
|
3
4
|
import { magicLinkPlugin } from "./magic-link/index.mjs";
|
|
4
5
|
import { oauthClientPlugin } from "./oauth/index.mjs";
|
|
5
|
-
import {
|
|
6
|
+
import { sessionJwtPlugin } from "./session-jwt/index.mjs";
|
|
6
7
|
import { twoFactorPlugin } from "./two-factor/index.mjs";
|
|
7
8
|
export { bearerPlugin, credentialPasswordPlugin, localStorageBearerStorage, magicLinkPlugin, memoryBearerStorage, oauthClientPlugin, resolveDefaultStorage, sessionJwtPlugin, twoFactorPlugin };
|
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteDescriptor } from "../../route.mjs";
|
|
2
|
+
import { Session } from "../../types.mjs";
|
|
3
|
+
import { ClientPlugin } from "../../define-plugin.mjs";
|
|
4
|
+
import { RequestMagicLinkInput, VerifyMagicLinkInput } from "./types.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/magic-link/index.d.ts
|
|
7
|
+
declare function magicLinkPlugin<TFields = unknown>(): ClientPlugin<"magic-link", "/magic-link", [RouteDescriptor<RequestMagicLinkInput, {
|
|
8
|
+
message: string;
|
|
9
|
+
}, {
|
|
10
|
+
readonly method: "POST";
|
|
11
|
+
readonly path: "/signin";
|
|
12
|
+
}>, RouteDescriptor<VerifyMagicLinkInput, Session<TFields>, {
|
|
13
|
+
readonly method: "GET";
|
|
14
|
+
readonly path: "/verify";
|
|
15
|
+
readonly parseSession: true;
|
|
16
|
+
}>], Record<never, never>>;
|
|
17
|
+
//#endregion
|
|
2
18
|
export { type RequestMagicLinkInput, type VerifyMagicLinkInput, magicLinkPlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { defineClientPlugin, defineRoutes } from "../../define-plugin.mjs";
|
|
2
|
+
import { route } from "../../route.mjs";
|
|
3
3
|
//#region src/plugins/magic-link/index.ts
|
|
4
4
|
function magicLinkPlugin() {
|
|
5
5
|
return defineClientPlugin({
|
|
@@ -17,5 +17,3 @@ function magicLinkPlugin() {
|
|
|
17
17
|
}
|
|
18
18
|
//#endregion
|
|
19
19
|
export { magicLinkPlugin };
|
|
20
|
-
|
|
21
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/plugins/magic-link/types.d.ts
|
|
2
|
+
type RequestMagicLinkInput = {
|
|
3
|
+
email: string;
|
|
4
|
+
redirectUri?: string;
|
|
5
|
+
newUserRedirectUri?: string;
|
|
6
|
+
errorRedirectUri?: string;
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
type VerifyMagicLinkInput = {
|
|
10
|
+
token: string;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { RequestMagicLinkInput, VerifyMagicLinkInput };
|
|
@@ -1,2 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteDescriptor, RouteHandler } from "../../route.mjs";
|
|
2
|
+
import { ClientPlugin } from "../../define-plugin.mjs";
|
|
3
|
+
import { camelizeKeys } from "../../helpers.mjs";
|
|
4
|
+
import { LinkOAuthInput, OAuthAccount, OAuthAuthorizeQuery, OAuthAuthorizeResult, OAuthTokens, SignInOAuthInput } from "./types.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/oauth/index.d.ts
|
|
7
|
+
declare function oauthClientPlugin(): ClientPlugin<"oauth", "/oauth", [RouteDescriptor<SignInOAuthInput, OAuthAuthorizeResult, {
|
|
8
|
+
readonly method: "GET";
|
|
9
|
+
readonly path: "/:provider/authorize";
|
|
10
|
+
readonly as: "signIn.social";
|
|
11
|
+
readonly params: readonly ["provider"];
|
|
12
|
+
readonly handler: RouteHandler<SignInOAuthInput, OAuthAuthorizeResult>;
|
|
13
|
+
}>, RouteDescriptor<SignInOAuthInput, OAuthAuthorizeResult, {
|
|
14
|
+
readonly method: "GET";
|
|
15
|
+
readonly path: "/:provider/link";
|
|
16
|
+
readonly as: "social.link";
|
|
17
|
+
readonly params: readonly ["provider"];
|
|
18
|
+
readonly handler: RouteHandler<SignInOAuthInput, OAuthAuthorizeResult>;
|
|
19
|
+
}>, RouteDescriptor<{
|
|
20
|
+
provider: string;
|
|
21
|
+
}, void, {
|
|
22
|
+
readonly method: "DELETE";
|
|
23
|
+
readonly path: "/:provider/unlink";
|
|
24
|
+
readonly as: "social.unlink";
|
|
25
|
+
readonly params: readonly ["provider"];
|
|
26
|
+
}>, RouteDescriptor<void, OAuthAccount[], {
|
|
27
|
+
readonly method: "GET";
|
|
28
|
+
readonly path: "/accounts";
|
|
29
|
+
readonly as: "social.accounts";
|
|
30
|
+
}>, RouteDescriptor<{
|
|
31
|
+
provider: string;
|
|
32
|
+
}, OAuthTokens, {
|
|
33
|
+
readonly method: "GET";
|
|
34
|
+
readonly path: "/:provider/tokens";
|
|
35
|
+
readonly as: "social.tokens";
|
|
36
|
+
readonly params: readonly ["provider"];
|
|
37
|
+
readonly parse: typeof camelizeKeys;
|
|
38
|
+
}>, RouteDescriptor<{
|
|
39
|
+
provider: string;
|
|
40
|
+
}, OAuthTokens, {
|
|
41
|
+
readonly method: "POST";
|
|
42
|
+
readonly path: "/:provider/tokens/refresh";
|
|
43
|
+
readonly as: "social.refreshTokens";
|
|
44
|
+
readonly params: readonly ["provider"];
|
|
45
|
+
readonly parse: typeof camelizeKeys;
|
|
46
|
+
}>], Record<never, never>>;
|
|
47
|
+
//#endregion
|
|
2
48
|
export { type LinkOAuthInput, type OAuthAccount, type OAuthAuthorizeQuery, type OAuthAuthorizeResult, type OAuthTokens, type SignInOAuthInput, oauthClientPlugin };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { camelizeKeys } from "../../helpers.mjs";
|
|
2
|
+
import { defineClientPlugin, defineRoutes } from "../../define-plugin.mjs";
|
|
3
|
+
import { route } from "../../route.mjs";
|
|
4
4
|
//#region src/plugins/oauth/index.ts
|
|
5
5
|
const fetchThenRedirect = async (ctx, input, http) => {
|
|
6
6
|
const { disableRedirect, ...rest } = input;
|
|
@@ -52,5 +52,3 @@ function oauthClientPlugin() {
|
|
|
52
52
|
}
|
|
53
53
|
//#endregion
|
|
54
54
|
export { oauthClientPlugin };
|
|
55
|
-
|
|
56
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/plugins/oauth/types.d.ts
|
|
2
|
+
type OAuthAuthorizeQuery = {
|
|
3
|
+
/** Where the server redirects the browser after a successful callback. */redirectUri?: string; /** Where the server redirects on a failed callback. Falls back to `redirectUri`. */
|
|
4
|
+
errorRedirectUri?: string;
|
|
5
|
+
};
|
|
6
|
+
type SignInOAuthInput = OAuthAuthorizeQuery & {
|
|
7
|
+
/** Provider id, e.g. `"google"` or `"github"`. */provider: string;
|
|
8
|
+
/**
|
|
9
|
+
* When true, skip auto-navigation and only resolve with the authorization
|
|
10
|
+
* URL — the caller is responsible for navigating the browser.
|
|
11
|
+
*/
|
|
12
|
+
disableRedirect?: boolean;
|
|
13
|
+
};
|
|
14
|
+
type LinkOAuthInput = SignInOAuthInput;
|
|
15
|
+
type OAuthAuthorizeResult = {
|
|
16
|
+
/** Provider authorization URL. */url: string; /** Whether the SDK navigated to `url`. */
|
|
17
|
+
redirect: boolean;
|
|
18
|
+
};
|
|
19
|
+
type OAuthAccount = {
|
|
20
|
+
provider: string;
|
|
21
|
+
providerAccountId: string;
|
|
22
|
+
scopes: string[];
|
|
23
|
+
accessTokenExpiresAt?: string;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
updatedAt: string;
|
|
26
|
+
};
|
|
27
|
+
type OAuthTokens = {
|
|
28
|
+
accessToken: string;
|
|
29
|
+
refreshToken?: string;
|
|
30
|
+
idToken?: string;
|
|
31
|
+
accessTokenExpiresAt?: string;
|
|
32
|
+
scope?: string;
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
export { LinkOAuthInput, OAuthAccount, OAuthAuthorizeQuery, OAuthAuthorizeResult, OAuthTokens, SignInOAuthInput };
|
|
@@ -1,2 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteDescriptor } from "../../route.mjs";
|
|
2
|
+
import { Session } from "../../types.mjs";
|
|
3
|
+
import { ClientPlugin } from "../../define-plugin.mjs";
|
|
4
|
+
import { BearerTokens } from "../bearer/types.mjs";
|
|
5
|
+
import { RefreshInput, SessionJwtPluginConfig, SessionJwtTokens } from "./types.mjs";
|
|
6
|
+
//#region src/plugins/session-jwt/index.d.ts
|
|
7
|
+
declare function sessionJwtPlugin<TFields = unknown>(config?: SessionJwtPluginConfig): ClientPlugin<"session-jwt", "", [RouteDescriptor<RefreshInput, Session<TFields>, {
|
|
8
|
+
readonly method: "POST";
|
|
9
|
+
readonly path: "/refresh";
|
|
10
|
+
readonly parseSession: true;
|
|
11
|
+
readonly expose: false;
|
|
12
|
+
}>], {
|
|
13
|
+
sessionJwt: {
|
|
14
|
+
/**
|
|
15
|
+
* Get the current access token. If the token is expiring, refresh it.
|
|
16
|
+
* @returns The current access token or null if no token is found.
|
|
17
|
+
*/
|
|
18
|
+
getAccessToken: () => Promise<string | null>;
|
|
19
|
+
refresh: () => Promise<SessionJwtTokens>;
|
|
20
|
+
getTokens: () => BearerTokens | null;
|
|
21
|
+
clear: () => void;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
//#endregion
|
|
2
25
|
export { type SessionJwtPluginConfig, type SessionJwtTokens, sessionJwtPlugin };
|
|
@@ -1,2 +1,96 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../../constants.mjs";
|
|
2
|
+
import { LimenError } from "../../errors.mjs";
|
|
3
|
+
import { defineClientPlugin, defineRoutes } from "../../define-plugin.mjs";
|
|
4
|
+
import { route } from "../../route.mjs";
|
|
5
|
+
import { resolveDefaultStorage } from "../bearer/storage.mjs";
|
|
6
|
+
import { isExpiring, tokensFromHeaders } from "./jwt.mjs";
|
|
7
|
+
//#region src/plugins/session-jwt/index.ts
|
|
8
|
+
function sessionJwtPlugin(config = {}) {
|
|
9
|
+
const store = config.storage ?? resolveDefaultStorage(config.storageKey ?? "limen.tokens");
|
|
10
|
+
const skewMs = (config.expirySkewSeconds ?? 30) * 1e3;
|
|
11
|
+
const refreshRoute = route()({
|
|
12
|
+
method: "POST",
|
|
13
|
+
path: "/refresh",
|
|
14
|
+
parseSession: true,
|
|
15
|
+
expose: false
|
|
16
|
+
});
|
|
17
|
+
let ctx;
|
|
18
|
+
let run;
|
|
19
|
+
let inFlight = null;
|
|
20
|
+
const runRefresh = async () => {
|
|
21
|
+
const current = store.get();
|
|
22
|
+
if (!current?.refreshToken) throw new LimenError("No refresh token found", 401, "unauthorized");
|
|
23
|
+
try {
|
|
24
|
+
await run(refreshRoute, { refreshToken: current.refreshToken });
|
|
25
|
+
const tokens = store.get();
|
|
26
|
+
if (!tokens?.accessToken) throw new LimenError("Refresh did not return a valid access token", 500, "unknown");
|
|
27
|
+
return tokens;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
store.clear();
|
|
30
|
+
ctx.setSession(null);
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const refresh = () => {
|
|
35
|
+
if (!inFlight) inFlight = runRefresh().finally(() => {
|
|
36
|
+
inFlight = null;
|
|
37
|
+
});
|
|
38
|
+
return inFlight;
|
|
39
|
+
};
|
|
40
|
+
const getAccessToken = async () => {
|
|
41
|
+
const current = store.get();
|
|
42
|
+
if (!current?.accessToken) return null;
|
|
43
|
+
if (!isExpiring(current.accessToken, skewMs)) return current.accessToken;
|
|
44
|
+
if (!current.refreshToken) return null;
|
|
45
|
+
return (await refresh()).accessToken;
|
|
46
|
+
};
|
|
47
|
+
const applyAuthHeader = async (req) => {
|
|
48
|
+
if (req.headers.has("Authorization")) return;
|
|
49
|
+
const token = await getAccessToken().catch(() => null);
|
|
50
|
+
if (token) req.headers.set("Authorization", `Bearer ${token}`);
|
|
51
|
+
};
|
|
52
|
+
return defineClientPlugin({
|
|
53
|
+
id: "session-jwt",
|
|
54
|
+
routes: defineRoutes(refreshRoute),
|
|
55
|
+
actions: (pluginCtx, pluginRun) => {
|
|
56
|
+
ctx = pluginCtx;
|
|
57
|
+
run = pluginRun;
|
|
58
|
+
return { sessionJwt: {
|
|
59
|
+
/**
|
|
60
|
+
* Get the current access token. If the token is expiring, refresh it.
|
|
61
|
+
* @returns The current access token or null if no token is found.
|
|
62
|
+
*/
|
|
63
|
+
getAccessToken,
|
|
64
|
+
refresh,
|
|
65
|
+
getTokens: () => store.get(),
|
|
66
|
+
clear: () => store.clear()
|
|
67
|
+
} };
|
|
68
|
+
},
|
|
69
|
+
hooks: {
|
|
70
|
+
beforeRequest: [{
|
|
71
|
+
match: (route) => route.path !== "/refresh",
|
|
72
|
+
run: async (req) => {
|
|
73
|
+
await applyAuthHeader(req);
|
|
74
|
+
return req;
|
|
75
|
+
}
|
|
76
|
+
}],
|
|
77
|
+
afterResponse: [{
|
|
78
|
+
allowOnFailure: true,
|
|
79
|
+
run: (res) => {
|
|
80
|
+
const tokens = tokensFromHeaders(res.headers);
|
|
81
|
+
if (tokens) store.set(tokens);
|
|
82
|
+
return res;
|
|
83
|
+
}
|
|
84
|
+
}, {
|
|
85
|
+
match: ["/signout", "/revoke-sessions"],
|
|
86
|
+
allowOnFailure: true,
|
|
87
|
+
run: (res) => {
|
|
88
|
+
store.clear();
|
|
89
|
+
return res;
|
|
90
|
+
}
|
|
91
|
+
}]
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
2
96
|
export { sessionJwtPlugin };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SET_AUTH_TOKEN_HEADER, SET_REFRESH_TOKEN_HEADER } from "../../constants.mjs";
|
|
2
|
+
//#region src/plugins/session-jwt/jwt.ts
|
|
3
|
+
/**
|
|
4
|
+
* Read the `exp` (seconds since epoch) from a JWT without verifying its
|
|
5
|
+
* signature — the server is the source of truth; the client only needs expiry
|
|
6
|
+
* to decide when to refresh. Returns `null` for anything malformed.
|
|
7
|
+
*/
|
|
8
|
+
function decodeJwtExp(token) {
|
|
9
|
+
const payload = token.split(".")[1];
|
|
10
|
+
if (!payload) return null;
|
|
11
|
+
try {
|
|
12
|
+
const b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
13
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
14
|
+
const claims = JSON.parse(atob(padded));
|
|
15
|
+
return typeof claims.exp === "number" ? claims.exp : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isExpiring(token, skewMs) {
|
|
21
|
+
const exp = decodeJwtExp(token);
|
|
22
|
+
if (exp === null) return true;
|
|
23
|
+
return exp * 1e3 - skewMs <= Date.now();
|
|
24
|
+
}
|
|
25
|
+
function tokensFromHeaders(headers) {
|
|
26
|
+
const accessToken = headers.get(SET_AUTH_TOKEN_HEADER);
|
|
27
|
+
if (!accessToken) return null;
|
|
28
|
+
const tokens = { accessToken };
|
|
29
|
+
const refreshToken = headers.get(SET_REFRESH_TOKEN_HEADER);
|
|
30
|
+
if (refreshToken) tokens.refreshToken = refreshToken;
|
|
31
|
+
return tokens;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { isExpiring, tokensFromHeaders };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BearerPluginConfig, BearerTokens } from "../bearer/types.mjs";
|
|
2
|
+
//#region src/plugins/session-jwt/types.d.ts
|
|
3
|
+
type SessionJwtTokens = BearerTokens;
|
|
4
|
+
type SessionJwtPluginConfig = BearerPluginConfig & {
|
|
5
|
+
/** Refresh once the access token is within this many seconds of expiry. Default 30. */expirySkewSeconds?: number;
|
|
6
|
+
};
|
|
7
|
+
type RefreshInput = {
|
|
8
|
+
refreshToken: string;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { RefreshInput, SessionJwtPluginConfig, SessionJwtTokens };
|
|
@@ -1,2 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RouteDescriptor } from "../../route.mjs";
|
|
2
|
+
import { Session } from "../../types.mjs";
|
|
3
|
+
import { ClientPlugin } from "../../define-plugin.mjs";
|
|
4
|
+
import { DisableTwoFactorInput, FinalizeSetupInput, InitiateSetupInput, SendOTPInput, TwoFactorConfig, TwoFactorMethod, TwoFactorSetupURI, VerifyInput } from "./types.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/two-factor/index.d.ts
|
|
7
|
+
declare function twoFactorPlugin<TFields = unknown>(config: TwoFactorConfig): ClientPlugin<"two-factor", "/two-factor", [RouteDescriptor<InitiateSetupInput, TwoFactorSetupURI, {
|
|
8
|
+
readonly method: "POST";
|
|
9
|
+
readonly path: "/initiate-setup";
|
|
10
|
+
}>, RouteDescriptor<FinalizeSetupInput, Session<TFields>, {
|
|
11
|
+
readonly method: "POST";
|
|
12
|
+
readonly path: "/finalize-setup";
|
|
13
|
+
readonly parseSession: true;
|
|
14
|
+
}>, RouteDescriptor<DisableTwoFactorInput, Session<TFields>, {
|
|
15
|
+
readonly method: "POST";
|
|
16
|
+
readonly path: "/disable";
|
|
17
|
+
readonly parseSession: true;
|
|
18
|
+
}>, RouteDescriptor<VerifyInput, Session<TFields>, {
|
|
19
|
+
readonly method: "POST";
|
|
20
|
+
readonly path: "/verify";
|
|
21
|
+
readonly parseSession: true;
|
|
22
|
+
}>, RouteDescriptor<void, TwoFactorSetupURI, {
|
|
23
|
+
readonly method: "GET";
|
|
24
|
+
readonly path: "/totp/uri";
|
|
25
|
+
readonly as: "twoFactor.getTotpUri";
|
|
26
|
+
}>, RouteDescriptor<void, string[], {
|
|
27
|
+
readonly method: "GET";
|
|
28
|
+
readonly path: "/backup-codes";
|
|
29
|
+
readonly as: "twoFactor.getBackupCodes";
|
|
30
|
+
}>, RouteDescriptor<void, string[], {
|
|
31
|
+
readonly method: "PUT";
|
|
32
|
+
readonly path: "/backup-codes";
|
|
33
|
+
readonly as: "twoFactor.regenerateBackupCodes";
|
|
34
|
+
}>, RouteDescriptor<SendOTPInput, {
|
|
35
|
+
message: string;
|
|
36
|
+
}, {
|
|
37
|
+
readonly method: "POST";
|
|
38
|
+
readonly path: "/otp/send";
|
|
39
|
+
readonly as: "twoFactor.sendOTP";
|
|
40
|
+
}>], Record<never, never>>;
|
|
41
|
+
//#endregion
|
|
2
42
|
export { type DisableTwoFactorInput, type FinalizeSetupInput, type InitiateSetupInput, type SendOTPInput, type TwoFactorConfig, type TwoFactorMethod, type TwoFactorSetupURI, type VerifyInput, twoFactorPlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { defineClientPlugin, defineRoutes } from "../../define-plugin.mjs";
|
|
2
|
+
import { route } from "../../route.mjs";
|
|
3
3
|
//#region src/plugins/two-factor/index.ts
|
|
4
4
|
function twoFactorPlugin(config) {
|
|
5
5
|
return defineClientPlugin({
|
|
@@ -48,5 +48,3 @@ function twoFactorPlugin(config) {
|
|
|
48
48
|
}
|
|
49
49
|
//#endregion
|
|
50
50
|
export { twoFactorPlugin };
|
|
51
|
-
|
|
52
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/plugins/two-factor/types.d.ts
|
|
2
|
+
type TwoFactorConfig = {
|
|
3
|
+
/** Called when sign-in requires a two-factor challenge. */onTwoFactorRedirect: () => void;
|
|
4
|
+
};
|
|
5
|
+
type VerifyInput = {
|
|
6
|
+
code: string;
|
|
7
|
+
method?: TwoFactorMethod;
|
|
8
|
+
};
|
|
9
|
+
type InitiateSetupInput = {
|
|
10
|
+
password: string;
|
|
11
|
+
};
|
|
12
|
+
type FinalizeSetupInput = {
|
|
13
|
+
code: string;
|
|
14
|
+
};
|
|
15
|
+
type DisableTwoFactorInput = {
|
|
16
|
+
password: string;
|
|
17
|
+
};
|
|
18
|
+
type SendOTPInput = {
|
|
19
|
+
email: string;
|
|
20
|
+
};
|
|
21
|
+
type TwoFactorSetupURI = {
|
|
22
|
+
uri: string;
|
|
23
|
+
};
|
|
24
|
+
type TwoFactorMethod = "totp" | "otp";
|
|
25
|
+
//#endregion
|
|
26
|
+
export { DisableTwoFactorInput, FinalizeSetupInput, InitiateSetupInput, SendOTPInput, TwoFactorConfig, TwoFactorMethod, TwoFactorSetupURI, VerifyInput };
|
package/dist/react/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Prettify } from "../type-utils.mjs";
|
|
2
|
+
import { SessionState, SessionStore } from "../session-store.mjs";
|
|
3
|
+
import { AuthClient, CreateAuthClientOptions, Session, User } from "../types.mjs";
|
|
4
|
+
import { AnyClientPlugin } from "../define-plugin.mjs";
|
|
5
|
+
import { useStore } from "./react-store.mjs";
|
|
3
6
|
|
|
4
|
-
//#region src/react/react-store.d.ts
|
|
5
|
-
declare function useStore<SomeStore extends Store>(store: SomeStore): StoreValue<SomeStore>;
|
|
6
|
-
//#endregion
|
|
7
7
|
//#region src/react/index.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* An {@link AuthClient} augmented with React hooks.
|
|
@@ -20,5 +20,4 @@ type ReactAuthClient<Plugins extends readonly AnyClientPlugin[], TFields = unkno
|
|
|
20
20
|
*/
|
|
21
21
|
declare function createAuthClient<const Plugins extends readonly AnyClientPlugin[] = readonly [], TFields = unknown>(opts: CreateAuthClientOptions<Plugins, TFields>): ReactAuthClient<Plugins, TFields>;
|
|
22
22
|
//#endregion
|
|
23
|
-
export { type AuthClient, type CreateAuthClientOptions, ReactAuthClient, type Session, type SessionState, type SessionStore, type User, createAuthClient, useStore };
|
|
24
|
-
//# sourceMappingURL=index.d.mts.map
|
|
23
|
+
export { type AuthClient, type CreateAuthClientOptions, ReactAuthClient, type Session, type SessionState, type SessionStore, type User, createAuthClient, useStore };
|
package/dist/react/index.mjs
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
//#region src/react/react-store.ts
|
|
4
|
-
function useStore(store) {
|
|
5
|
-
const snapshotRef = useRef(store.get());
|
|
6
|
-
const subscribe = useCallback((onChange) => {
|
|
7
|
-
const emitValue = (value) => {
|
|
8
|
-
if (snapshotRef.current === value) return;
|
|
9
|
-
snapshotRef.current = value;
|
|
10
|
-
onChange();
|
|
11
|
-
};
|
|
12
|
-
emitValue(store.value);
|
|
13
|
-
return store.listen(emitValue);
|
|
14
|
-
}, [store]);
|
|
15
|
-
const get = () => snapshotRef.current;
|
|
16
|
-
return useSyncExternalStore(subscribe, get, get);
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
1
|
+
import { createAuthClient as createAuthClient$1 } from "../client.mjs";
|
|
2
|
+
import { useStore } from "./react-store.mjs";
|
|
19
3
|
//#region src/react/index.ts
|
|
20
4
|
/**
|
|
21
5
|
* Create a Limen auth client with React hooks attached.
|
|
@@ -27,5 +11,3 @@ function createAuthClient(opts) {
|
|
|
27
11
|
}
|
|
28
12
|
//#endregion
|
|
29
13
|
export { createAuthClient, useStore };
|
|
30
|
-
|
|
31
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/react/react-store.ts
|
|
3
|
+
function useStore(store) {
|
|
4
|
+
const snapshotRef = useRef(store.get());
|
|
5
|
+
const subscribe = useCallback((onChange) => {
|
|
6
|
+
const emitValue = (value) => {
|
|
7
|
+
if (snapshotRef.current === value) return;
|
|
8
|
+
snapshotRef.current = value;
|
|
9
|
+
onChange();
|
|
10
|
+
};
|
|
11
|
+
emitValue(store.value);
|
|
12
|
+
return store.listen(emitValue);
|
|
13
|
+
}, [store]);
|
|
14
|
+
const get = () => snapshotRef.current;
|
|
15
|
+
return useSyncExternalStore(subscribe, get, get);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { useStore };
|
package/dist/route.d.mts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { LimenError } from "./errors.mjs";
|
|
2
|
+
import { HTTPMethod } from "./types.mjs";
|
|
3
|
+
import { RouteContext } from "./context.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/route.d.ts
|
|
6
|
+
declare const INPUT: unique symbol;
|
|
7
|
+
declare const OUTPUT: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* Runs the route's default HTTP request and parser without session effects.
|
|
10
|
+
* Pass an input override when a handler needs to omit client-only fields.
|
|
11
|
+
*/
|
|
12
|
+
type HttpRunner<I> = <R = unknown>(input?: I) => Promise<R>;
|
|
13
|
+
/**
|
|
14
|
+
* Custom route behavior for flows that need more than the default request
|
|
15
|
+
* pipeline.
|
|
16
|
+
*/
|
|
17
|
+
type RouteHandler<I, O, TFields = unknown> = (ctx: RouteContext<TFields>, input: I, http: HttpRunner<I>) => Promise<O>;
|
|
18
|
+
/**
|
|
19
|
+
* Per-call options accepted as the final argument of every generated route
|
|
20
|
+
* method.
|
|
21
|
+
*/
|
|
22
|
+
type RouteCallOptions<O = unknown> = {
|
|
23
|
+
/** Invoked with the resolved value after the call succeeds. */onSuccess?: (data: O) => void; /** Invoked with the error just before it is re-thrown. */
|
|
24
|
+
onError?: (error: LimenError) => void;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Declarative client route definition. The public client chain is derived from
|
|
28
|
+
* `path` unless `as` is provided.
|
|
29
|
+
*/
|
|
30
|
+
type RouteDef<I, O> = {
|
|
31
|
+
method: HTTPMethod;
|
|
32
|
+
path: `/${string}`; /** Dotted client chain override, e.g. `"twoFactor.getTotpUri"`. */
|
|
33
|
+
as?: string; /** Merged under the caller's input before serialization. */
|
|
34
|
+
defaults?: Partial<I>; /** SDK input → wire body/query. Defaults to shallow camelCase → snake_case. */
|
|
35
|
+
serialize?: (input: I) => unknown; /** Raw response → typed output. Ignored when `parseSession` is set. */
|
|
36
|
+
parse?: (raw: unknown) => O;
|
|
37
|
+
/**
|
|
38
|
+
* Parse the response as a session and store it when it contains a `user`.
|
|
39
|
+
* Set `skipStore` to return the parsed session without writing it.
|
|
40
|
+
*/
|
|
41
|
+
parseSession?: boolean; /** Resolve `path` from the client base path instead of the plugin base path. */
|
|
42
|
+
absolute?: boolean; /** Input keys used as `:param` path values. */
|
|
43
|
+
params?: readonly (keyof I & string)[]; /** For `parseSession` routes, skip the session-store write. */
|
|
44
|
+
skipStore?: boolean; /** Clear the session store on success. */
|
|
45
|
+
clearSession?: boolean; /** Revalidate the session after success. */
|
|
46
|
+
refetchSession?: boolean; /** Set `false` to keep the route out of the public client API. */
|
|
47
|
+
expose?: boolean; /** Override the default route call behavior. */
|
|
48
|
+
handler?: RouteHandler<I, O>;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* A route definition carrying its input and output types for inference.
|
|
52
|
+
*/
|
|
53
|
+
type RouteDescriptor<I, O, D extends RouteDef<I, O> = RouteDef<I, O>> = D & {
|
|
54
|
+
readonly [INPUT]: I;
|
|
55
|
+
readonly [OUTPUT]: O;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Loose route-descriptor constraint for route tuples and plugin definitions.
|
|
59
|
+
*/
|
|
60
|
+
type AnyRouteDescriptor = RouteDescriptor<any, any, any>;
|
|
61
|
+
type InputOf<R> = R extends {
|
|
62
|
+
readonly [INPUT]: infer I;
|
|
63
|
+
} ? I : never;
|
|
64
|
+
type OutputOf<R> = R extends {
|
|
65
|
+
readonly [OUTPUT]: infer O;
|
|
66
|
+
} ? O : never;
|
|
67
|
+
/**
|
|
68
|
+
* Define an HTTP-backed client method for a plugin. The input/output types
|
|
69
|
+
* become the generated method's argument and resolved value.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* route<VerifyInput, Session<TFields>>()({
|
|
73
|
+
* method: "POST",
|
|
74
|
+
* path: "/verify",
|
|
75
|
+
* parseSession: true,
|
|
76
|
+
* })
|
|
77
|
+
*/
|
|
78
|
+
declare function route<I = void, O = unknown>(): <const D extends RouteDef<I, O>>(def: D) => RouteDescriptor<I, O, D>;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { AnyRouteDescriptor, InputOf, OutputOf, RouteCallOptions, RouteDescriptor, RouteHandler, route };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RouteDescriptor } from "./route.mjs";
|
|
2
|
+
import { Session } from "./types.mjs";
|
|
3
|
+
import { ClientPlugin, InferPluginContribution } from "./define-plugin.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/routes.d.ts
|
|
6
|
+
type VerifyEmailInput = {
|
|
7
|
+
token: string;
|
|
8
|
+
};
|
|
9
|
+
type ActiveSession = {
|
|
10
|
+
id: string | number;
|
|
11
|
+
token: string;
|
|
12
|
+
userId: unknown;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
lastAccess: string;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Core routes available on every client.
|
|
20
|
+
*/
|
|
21
|
+
declare function coreClientPlugin<TFields = unknown>(): ClientPlugin<"core", "/", [RouteDescriptor<void, ActiveSession[], {
|
|
22
|
+
readonly method: "GET";
|
|
23
|
+
readonly path: "/sessions";
|
|
24
|
+
}>, RouteDescriptor<void, void, {
|
|
25
|
+
readonly method: "POST";
|
|
26
|
+
readonly path: "/signout";
|
|
27
|
+
readonly clearSession: true;
|
|
28
|
+
}>, RouteDescriptor<void, void, {
|
|
29
|
+
readonly method: "POST";
|
|
30
|
+
readonly path: "/revoke-sessions";
|
|
31
|
+
readonly clearSession: true;
|
|
32
|
+
}>, RouteDescriptor<VerifyEmailInput, string, {
|
|
33
|
+
readonly method: "POST";
|
|
34
|
+
readonly path: "/verify-email";
|
|
35
|
+
readonly refetchSession: true;
|
|
36
|
+
}>, RouteDescriptor<void, string, {
|
|
37
|
+
readonly method: "POST";
|
|
38
|
+
readonly path: "/email-verifications";
|
|
39
|
+
readonly as: "requestEmailVerification";
|
|
40
|
+
}>], {
|
|
41
|
+
/**
|
|
42
|
+
* Revalidate session state with `GET /me`, update `$session`, and return the
|
|
43
|
+
* resolved value (`null` when signed out).
|
|
44
|
+
*
|
|
45
|
+
* Prefer subscribing to `$session` for reactive UI state (`data`, `isPending`,
|
|
46
|
+
* `error`). Use `getSession()` when you need an awaited server re-check, such
|
|
47
|
+
* as route guards or SSR revalidation after `initialSession`.
|
|
48
|
+
*/
|
|
49
|
+
getSession: () => Promise<Session<TFields> | null>;
|
|
50
|
+
}>;
|
|
51
|
+
type CoreContribution<TFields = unknown> = InferPluginContribution<ReturnType<typeof coreClientPlugin<TFields>>>;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { ActiveSession, CoreContribution, VerifyEmailInput, coreClientPlugin };
|