@weirdfingers/boards-auth-jwt 0.9.13

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,53 @@
1
+ import * as _weirdfingers_boards from '@weirdfingers/boards';
2
+ import { AuthProviderConfig, BaseAuthProvider, AuthState, User } from '@weirdfingers/boards';
3
+ export { AuthContextValue, AuthProviderConfig, AuthState, User } from '@weirdfingers/boards';
4
+
5
+ /**
6
+ * JWT auth provider types.
7
+ */
8
+
9
+ interface JWTConfig extends AuthProviderConfig {
10
+ /**
11
+ * API endpoint for authentication operations.
12
+ */
13
+ apiUrl: string;
14
+ /**
15
+ * Storage key for the JWT token.
16
+ * Defaults to 'boards_jwt_token'.
17
+ */
18
+ tokenStorageKey?: string;
19
+ /**
20
+ * Storage key for user information.
21
+ * Defaults to 'boards_user_info'.
22
+ */
23
+ userStorageKey?: string;
24
+ }
25
+
26
+ declare class JWTAuthProvider extends BaseAuthProvider {
27
+ protected config: JWTConfig;
28
+ private listeners;
29
+ private currentState;
30
+ constructor(config: JWTConfig);
31
+ initialize(): Promise<void>;
32
+ getAuthState(): Promise<AuthState>;
33
+ signIn(options?: _weirdfingers_boards.SignInOptions): Promise<void>;
34
+ signOut(): Promise<void>;
35
+ getToken(): Promise<string | null>;
36
+ refreshToken(): Promise<string | null>;
37
+ getUser(): Promise<User | null>;
38
+ onAuthStateChange(callback: (state: AuthState) => void): () => void;
39
+ destroy(): Promise<void>;
40
+ private getUserFromToken;
41
+ /**
42
+ * Parse a JWT and return its payload, with robust error handling.
43
+ */
44
+ private parseJWT;
45
+ /**
46
+ * Decodes a base64url-encoded string.
47
+ */
48
+ private base64UrlDecode;
49
+ private isTokenExpired;
50
+ private updateState;
51
+ }
52
+
53
+ export { JWTAuthProvider, type JWTConfig };
@@ -0,0 +1,53 @@
1
+ import * as _weirdfingers_boards from '@weirdfingers/boards';
2
+ import { AuthProviderConfig, BaseAuthProvider, AuthState, User } from '@weirdfingers/boards';
3
+ export { AuthContextValue, AuthProviderConfig, AuthState, User } from '@weirdfingers/boards';
4
+
5
+ /**
6
+ * JWT auth provider types.
7
+ */
8
+
9
+ interface JWTConfig extends AuthProviderConfig {
10
+ /**
11
+ * API endpoint for authentication operations.
12
+ */
13
+ apiUrl: string;
14
+ /**
15
+ * Storage key for the JWT token.
16
+ * Defaults to 'boards_jwt_token'.
17
+ */
18
+ tokenStorageKey?: string;
19
+ /**
20
+ * Storage key for user information.
21
+ * Defaults to 'boards_user_info'.
22
+ */
23
+ userStorageKey?: string;
24
+ }
25
+
26
+ declare class JWTAuthProvider extends BaseAuthProvider {
27
+ protected config: JWTConfig;
28
+ private listeners;
29
+ private currentState;
30
+ constructor(config: JWTConfig);
31
+ initialize(): Promise<void>;
32
+ getAuthState(): Promise<AuthState>;
33
+ signIn(options?: _weirdfingers_boards.SignInOptions): Promise<void>;
34
+ signOut(): Promise<void>;
35
+ getToken(): Promise<string | null>;
36
+ refreshToken(): Promise<string | null>;
37
+ getUser(): Promise<User | null>;
38
+ onAuthStateChange(callback: (state: AuthState) => void): () => void;
39
+ destroy(): Promise<void>;
40
+ private getUserFromToken;
41
+ /**
42
+ * Parse a JWT and return its payload, with robust error handling.
43
+ */
44
+ private parseJWT;
45
+ /**
46
+ * Decodes a base64url-encoded string.
47
+ */
48
+ private base64UrlDecode;
49
+ private isTokenExpired;
50
+ private updateState;
51
+ }
52
+
53
+ export { JWTAuthProvider, type JWTConfig };
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ JWTAuthProvider: () => JWTAuthProvider
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/JWTAuthProvider.ts
28
+ var import_boards = require("@weirdfingers/boards");
29
+ var JWTAuthProvider = class extends import_boards.BaseAuthProvider {
30
+ constructor(config) {
31
+ super(config);
32
+ this.listeners = [];
33
+ this.config = {
34
+ tokenStorageKey: "boards_jwt_token",
35
+ userStorageKey: "boards_user_info",
36
+ ...config
37
+ };
38
+ this.currentState = {
39
+ user: null,
40
+ status: "loading",
41
+ signIn: this.signIn.bind(this),
42
+ signOut: this.signOut.bind(this),
43
+ getToken: this.getToken.bind(this),
44
+ refreshToken: this.refreshToken.bind(this)
45
+ };
46
+ }
47
+ async initialize() {
48
+ const token = localStorage.getItem(this.config.tokenStorageKey);
49
+ if (token) {
50
+ try {
51
+ const user = await this.getUserFromToken(token);
52
+ if (user && !this.isTokenExpired(token)) {
53
+ this.updateState({ user, status: "authenticated" });
54
+ return;
55
+ }
56
+ } catch (error) {
57
+ console.warn("Failed to restore user session:", error);
58
+ }
59
+ }
60
+ this.updateState({ user: null, status: "unauthenticated" });
61
+ }
62
+ async getAuthState() {
63
+ return this.currentState;
64
+ }
65
+ async signIn(options) {
66
+ const email = options?.email;
67
+ const password = options?.password;
68
+ this.updateState({ status: "loading" });
69
+ try {
70
+ const response = await fetch(`${this.config.apiUrl}/auth/login`, {
71
+ method: "POST",
72
+ headers: {
73
+ "Content-Type": "application/json",
74
+ ...this.config.tenantId && { "X-Tenant": this.config.tenantId }
75
+ },
76
+ body: JSON.stringify({ email, password })
77
+ });
78
+ if (!response.ok) {
79
+ throw new Error(`Authentication failed: ${response.statusText}`);
80
+ }
81
+ const data = await response.json();
82
+ const { token } = data;
83
+ if (!token) {
84
+ throw new Error("No token received from server");
85
+ }
86
+ localStorage.setItem(this.config.tokenStorageKey, token);
87
+ const user = await this.getUserFromToken(token);
88
+ this.updateState({ user, status: "authenticated" });
89
+ } catch (error) {
90
+ this.updateState({ user: null, status: "unauthenticated" });
91
+ throw error;
92
+ }
93
+ }
94
+ async signOut() {
95
+ localStorage.removeItem(this.config.tokenStorageKey);
96
+ localStorage.removeItem(this.config.userStorageKey);
97
+ this.updateState({ user: null, status: "unauthenticated" });
98
+ }
99
+ async getToken() {
100
+ const token = localStorage.getItem(this.config.tokenStorageKey);
101
+ if (!token || this.isTokenExpired(token)) {
102
+ return null;
103
+ }
104
+ return token;
105
+ }
106
+ async refreshToken() {
107
+ return this.getToken();
108
+ }
109
+ async getUser() {
110
+ return this.currentState.user;
111
+ }
112
+ onAuthStateChange(callback) {
113
+ this.listeners.push(callback);
114
+ return () => {
115
+ const index = this.listeners.indexOf(callback);
116
+ if (index > -1) {
117
+ this.listeners.splice(index, 1);
118
+ }
119
+ };
120
+ }
121
+ async destroy() {
122
+ this.listeners = [];
123
+ }
124
+ async getUserFromToken(token) {
125
+ try {
126
+ const payload = this.parseJWT(token);
127
+ return {
128
+ id: payload.sub,
129
+ email: payload.email ?? "",
130
+ name: payload.name,
131
+ avatar: payload.picture,
132
+ metadata: { provider: "jwt", subject: payload.sub },
133
+ credits: { balance: 0, reserved: 0 }
134
+ };
135
+ } catch (error) {
136
+ console.error("Failed to parse user from token:", error);
137
+ return null;
138
+ }
139
+ }
140
+ /**
141
+ * Parse a JWT and return its payload, with robust error handling.
142
+ */
143
+ parseJWT(token) {
144
+ const parts = token.split(".");
145
+ if (parts.length !== 3) {
146
+ throw new Error(
147
+ "Invalid JWT format: token must have exactly 3 parts separated by dots"
148
+ );
149
+ }
150
+ try {
151
+ const payloadJson = this.base64UrlDecode(parts[1]);
152
+ const payload = JSON.parse(payloadJson);
153
+ if (typeof payload !== "object" || payload === null) {
154
+ throw new Error("Invalid JWT payload: payload must be a JSON object");
155
+ }
156
+ return payload;
157
+ } catch (err) {
158
+ if (err instanceof Error && err.message.startsWith("Invalid JWT")) {
159
+ throw err;
160
+ }
161
+ throw new Error(
162
+ "Failed to decode or parse JWT payload: " + (err instanceof Error ? err.message : String(err))
163
+ );
164
+ }
165
+ }
166
+ /**
167
+ * Decodes a base64url-encoded string.
168
+ */
169
+ base64UrlDecode(input) {
170
+ input = input.replace(/-/g, "+").replace(/_/g, "/");
171
+ const pad = input.length % 4;
172
+ if (pad) {
173
+ if (pad === 1) {
174
+ throw new Error("Invalid base64url encoding: incorrect padding");
175
+ }
176
+ input += "=".repeat(4 - pad);
177
+ }
178
+ try {
179
+ return atob(input);
180
+ } catch (e) {
181
+ throw new Error(
182
+ "Invalid base64url encoding: " + (e instanceof Error ? e.message : String(e))
183
+ );
184
+ }
185
+ }
186
+ isTokenExpired(token) {
187
+ try {
188
+ const payload = this.parseJWT(token);
189
+ const now = Math.floor(Date.now() / 1e3);
190
+ return payload.exp < now;
191
+ } catch {
192
+ return true;
193
+ }
194
+ }
195
+ updateState(updates) {
196
+ this.currentState = { ...this.currentState, ...updates };
197
+ this.listeners.forEach((listener) => listener(this.currentState));
198
+ }
199
+ };
200
+ // Annotate the CommonJS export names for ESM import in node:
201
+ 0 && (module.exports = {
202
+ JWTAuthProvider
203
+ });
204
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/JWTAuthProvider.ts"],"sourcesContent":["/**\n * JWT authentication provider package for Boards.\n */\n\nexport { JWTAuthProvider } from './JWTAuthProvider';\nexport type { JWTConfig } from './types';\n\n// Re-export core types for convenience\nexport type {\n AuthState,\n User,\n AuthProviderConfig,\n AuthContextValue\n} from '@weirdfingers/boards';\n","/**\n * JWT authentication provider for self-issued tokens.\n */\n\nimport { BaseAuthProvider, AuthState, User } from \"@weirdfingers/boards\";\nimport type { JWTConfig, JWTPayload } from \"./types\";\n\nexport class JWTAuthProvider extends BaseAuthProvider {\n protected config: JWTConfig;\n private listeners: ((state: AuthState) => void)[] = [];\n private currentState: AuthState;\n\n constructor(config: JWTConfig) {\n super(config);\n this.config = {\n tokenStorageKey: \"boards_jwt_token\",\n userStorageKey: \"boards_user_info\",\n ...config,\n };\n\n this.currentState = {\n user: null,\n status: \"loading\",\n signIn: this.signIn.bind(this) as any,\n signOut: this.signOut.bind(this),\n getToken: this.getToken.bind(this),\n refreshToken: this.refreshToken.bind(this),\n };\n }\n\n async initialize(): Promise<void> {\n // Check for existing token\n const token = localStorage.getItem(this.config.tokenStorageKey!);\n if (token) {\n try {\n const user = await this.getUserFromToken(token);\n if (user && !this.isTokenExpired(token)) {\n this.updateState({ user, status: \"authenticated\" });\n return;\n }\n } catch (error) {\n console.warn(\"Failed to restore user session:\", error);\n }\n }\n\n this.updateState({ user: null, status: \"unauthenticated\" });\n }\n\n async getAuthState(): Promise<AuthState> {\n return this.currentState;\n }\n\n async signIn(\n options?: import(\"@weirdfingers/boards\").SignInOptions\n ): Promise<void> {\n const email = (options as any)?.email as string | undefined;\n const password = (options as any)?.password as string | undefined;\n this.updateState({ status: \"loading\" });\n\n try {\n const response = await fetch(`${this.config.apiUrl}/auth/login`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this.config.tenantId && { \"X-Tenant\": this.config.tenantId }),\n },\n body: JSON.stringify({ email, password }),\n });\n\n if (!response.ok) {\n throw new Error(`Authentication failed: ${response.statusText}`);\n }\n\n const data = await response.json();\n const { token } = data;\n\n if (!token) {\n throw new Error(\"No token received from server\");\n }\n\n // Store token\n localStorage.setItem(this.config.tokenStorageKey!, token);\n\n // Get user info from token\n const user = await this.getUserFromToken(token);\n this.updateState({ user, status: \"authenticated\" });\n } catch (error) {\n this.updateState({ user: null, status: \"unauthenticated\" });\n throw error;\n }\n }\n\n async signOut(): Promise<void> {\n // Clear stored data\n localStorage.removeItem(this.config.tokenStorageKey!);\n localStorage.removeItem(this.config.userStorageKey!);\n\n this.updateState({ user: null, status: \"unauthenticated\" });\n }\n\n async getToken(): Promise<string | null> {\n const token = localStorage.getItem(this.config.tokenStorageKey!);\n if (!token || this.isTokenExpired(token)) {\n return null;\n }\n return token;\n }\n\n async refreshToken(): Promise<string | null> {\n // For JWT provider, there may be no refresh flow; return current token if valid\n return this.getToken();\n }\n\n async getUser(): Promise<User | null> {\n return this.currentState.user;\n }\n\n onAuthStateChange(callback: (state: AuthState) => void): () => void {\n this.listeners.push(callback);\n return () => {\n const index = this.listeners.indexOf(callback);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n async destroy(): Promise<void> {\n this.listeners = [];\n }\n\n private async getUserFromToken(token: string): Promise<User | null> {\n try {\n const payload = this.parseJWT(token);\n\n return {\n id: payload.sub,\n email: payload.email ?? \"\",\n name: payload.name,\n avatar: payload.picture,\n metadata: { provider: \"jwt\", subject: payload.sub },\n credits: { balance: 0, reserved: 0 },\n };\n } catch (error) {\n console.error(\"Failed to parse user from token:\", error);\n return null;\n }\n }\n\n /**\n * Parse a JWT and return its payload, with robust error handling.\n */\n private parseJWT(token: string): JWTPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\n \"Invalid JWT format: token must have exactly 3 parts separated by dots\"\n );\n }\n\n try {\n const payloadJson = this.base64UrlDecode(parts[1]);\n const payload = JSON.parse(payloadJson);\n\n if (typeof payload !== \"object\" || payload === null) {\n throw new Error(\"Invalid JWT payload: payload must be a JSON object\");\n }\n\n return payload;\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"Invalid JWT\")) {\n // Re-throw our own validation errors\n throw err;\n }\n throw new Error(\n \"Failed to decode or parse JWT payload: \" +\n (err instanceof Error ? err.message : String(err))\n );\n }\n }\n\n /**\n * Decodes a base64url-encoded string.\n */\n private base64UrlDecode(input: string): string {\n // Replace non-url compatible chars with base64 standard chars\n input = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\n // Pad out with standard base64 required padding characters\n const pad = input.length % 4;\n if (pad) {\n if (pad === 1) {\n throw new Error(\"Invalid base64url encoding: incorrect padding\");\n }\n input += \"=\".repeat(4 - pad);\n }\n\n try {\n return atob(input);\n } catch (e) {\n throw new Error(\n \"Invalid base64url encoding: \" +\n (e instanceof Error ? e.message : String(e))\n );\n }\n }\n\n private isTokenExpired(token: string): boolean {\n try {\n const payload = this.parseJWT(token);\n const now = Math.floor(Date.now() / 1000);\n return payload.exp < now;\n } catch {\n return true;\n }\n }\n\n private updateState(updates: Partial<AuthState>): void {\n this.currentState = { ...this.currentState, ...updates };\n this.listeners.forEach((listener) => listener(this.currentState));\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,177 @@
1
+ // src/JWTAuthProvider.ts
2
+ import { BaseAuthProvider } from "@weirdfingers/boards";
3
+ var JWTAuthProvider = class extends BaseAuthProvider {
4
+ constructor(config) {
5
+ super(config);
6
+ this.listeners = [];
7
+ this.config = {
8
+ tokenStorageKey: "boards_jwt_token",
9
+ userStorageKey: "boards_user_info",
10
+ ...config
11
+ };
12
+ this.currentState = {
13
+ user: null,
14
+ status: "loading",
15
+ signIn: this.signIn.bind(this),
16
+ signOut: this.signOut.bind(this),
17
+ getToken: this.getToken.bind(this),
18
+ refreshToken: this.refreshToken.bind(this)
19
+ };
20
+ }
21
+ async initialize() {
22
+ const token = localStorage.getItem(this.config.tokenStorageKey);
23
+ if (token) {
24
+ try {
25
+ const user = await this.getUserFromToken(token);
26
+ if (user && !this.isTokenExpired(token)) {
27
+ this.updateState({ user, status: "authenticated" });
28
+ return;
29
+ }
30
+ } catch (error) {
31
+ console.warn("Failed to restore user session:", error);
32
+ }
33
+ }
34
+ this.updateState({ user: null, status: "unauthenticated" });
35
+ }
36
+ async getAuthState() {
37
+ return this.currentState;
38
+ }
39
+ async signIn(options) {
40
+ const email = options?.email;
41
+ const password = options?.password;
42
+ this.updateState({ status: "loading" });
43
+ try {
44
+ const response = await fetch(`${this.config.apiUrl}/auth/login`, {
45
+ method: "POST",
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ ...this.config.tenantId && { "X-Tenant": this.config.tenantId }
49
+ },
50
+ body: JSON.stringify({ email, password })
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`Authentication failed: ${response.statusText}`);
54
+ }
55
+ const data = await response.json();
56
+ const { token } = data;
57
+ if (!token) {
58
+ throw new Error("No token received from server");
59
+ }
60
+ localStorage.setItem(this.config.tokenStorageKey, token);
61
+ const user = await this.getUserFromToken(token);
62
+ this.updateState({ user, status: "authenticated" });
63
+ } catch (error) {
64
+ this.updateState({ user: null, status: "unauthenticated" });
65
+ throw error;
66
+ }
67
+ }
68
+ async signOut() {
69
+ localStorage.removeItem(this.config.tokenStorageKey);
70
+ localStorage.removeItem(this.config.userStorageKey);
71
+ this.updateState({ user: null, status: "unauthenticated" });
72
+ }
73
+ async getToken() {
74
+ const token = localStorage.getItem(this.config.tokenStorageKey);
75
+ if (!token || this.isTokenExpired(token)) {
76
+ return null;
77
+ }
78
+ return token;
79
+ }
80
+ async refreshToken() {
81
+ return this.getToken();
82
+ }
83
+ async getUser() {
84
+ return this.currentState.user;
85
+ }
86
+ onAuthStateChange(callback) {
87
+ this.listeners.push(callback);
88
+ return () => {
89
+ const index = this.listeners.indexOf(callback);
90
+ if (index > -1) {
91
+ this.listeners.splice(index, 1);
92
+ }
93
+ };
94
+ }
95
+ async destroy() {
96
+ this.listeners = [];
97
+ }
98
+ async getUserFromToken(token) {
99
+ try {
100
+ const payload = this.parseJWT(token);
101
+ return {
102
+ id: payload.sub,
103
+ email: payload.email ?? "",
104
+ name: payload.name,
105
+ avatar: payload.picture,
106
+ metadata: { provider: "jwt", subject: payload.sub },
107
+ credits: { balance: 0, reserved: 0 }
108
+ };
109
+ } catch (error) {
110
+ console.error("Failed to parse user from token:", error);
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Parse a JWT and return its payload, with robust error handling.
116
+ */
117
+ parseJWT(token) {
118
+ const parts = token.split(".");
119
+ if (parts.length !== 3) {
120
+ throw new Error(
121
+ "Invalid JWT format: token must have exactly 3 parts separated by dots"
122
+ );
123
+ }
124
+ try {
125
+ const payloadJson = this.base64UrlDecode(parts[1]);
126
+ const payload = JSON.parse(payloadJson);
127
+ if (typeof payload !== "object" || payload === null) {
128
+ throw new Error("Invalid JWT payload: payload must be a JSON object");
129
+ }
130
+ return payload;
131
+ } catch (err) {
132
+ if (err instanceof Error && err.message.startsWith("Invalid JWT")) {
133
+ throw err;
134
+ }
135
+ throw new Error(
136
+ "Failed to decode or parse JWT payload: " + (err instanceof Error ? err.message : String(err))
137
+ );
138
+ }
139
+ }
140
+ /**
141
+ * Decodes a base64url-encoded string.
142
+ */
143
+ base64UrlDecode(input) {
144
+ input = input.replace(/-/g, "+").replace(/_/g, "/");
145
+ const pad = input.length % 4;
146
+ if (pad) {
147
+ if (pad === 1) {
148
+ throw new Error("Invalid base64url encoding: incorrect padding");
149
+ }
150
+ input += "=".repeat(4 - pad);
151
+ }
152
+ try {
153
+ return atob(input);
154
+ } catch (e) {
155
+ throw new Error(
156
+ "Invalid base64url encoding: " + (e instanceof Error ? e.message : String(e))
157
+ );
158
+ }
159
+ }
160
+ isTokenExpired(token) {
161
+ try {
162
+ const payload = this.parseJWT(token);
163
+ const now = Math.floor(Date.now() / 1e3);
164
+ return payload.exp < now;
165
+ } catch {
166
+ return true;
167
+ }
168
+ }
169
+ updateState(updates) {
170
+ this.currentState = { ...this.currentState, ...updates };
171
+ this.listeners.forEach((listener) => listener(this.currentState));
172
+ }
173
+ };
174
+ export {
175
+ JWTAuthProvider
176
+ };
177
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/JWTAuthProvider.ts"],"sourcesContent":["/**\n * JWT authentication provider for self-issued tokens.\n */\n\nimport { BaseAuthProvider, AuthState, User } from \"@weirdfingers/boards\";\nimport type { JWTConfig, JWTPayload } from \"./types\";\n\nexport class JWTAuthProvider extends BaseAuthProvider {\n protected config: JWTConfig;\n private listeners: ((state: AuthState) => void)[] = [];\n private currentState: AuthState;\n\n constructor(config: JWTConfig) {\n super(config);\n this.config = {\n tokenStorageKey: \"boards_jwt_token\",\n userStorageKey: \"boards_user_info\",\n ...config,\n };\n\n this.currentState = {\n user: null,\n status: \"loading\",\n signIn: this.signIn.bind(this) as any,\n signOut: this.signOut.bind(this),\n getToken: this.getToken.bind(this),\n refreshToken: this.refreshToken.bind(this),\n };\n }\n\n async initialize(): Promise<void> {\n // Check for existing token\n const token = localStorage.getItem(this.config.tokenStorageKey!);\n if (token) {\n try {\n const user = await this.getUserFromToken(token);\n if (user && !this.isTokenExpired(token)) {\n this.updateState({ user, status: \"authenticated\" });\n return;\n }\n } catch (error) {\n console.warn(\"Failed to restore user session:\", error);\n }\n }\n\n this.updateState({ user: null, status: \"unauthenticated\" });\n }\n\n async getAuthState(): Promise<AuthState> {\n return this.currentState;\n }\n\n async signIn(\n options?: import(\"@weirdfingers/boards\").SignInOptions\n ): Promise<void> {\n const email = (options as any)?.email as string | undefined;\n const password = (options as any)?.password as string | undefined;\n this.updateState({ status: \"loading\" });\n\n try {\n const response = await fetch(`${this.config.apiUrl}/auth/login`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this.config.tenantId && { \"X-Tenant\": this.config.tenantId }),\n },\n body: JSON.stringify({ email, password }),\n });\n\n if (!response.ok) {\n throw new Error(`Authentication failed: ${response.statusText}`);\n }\n\n const data = await response.json();\n const { token } = data;\n\n if (!token) {\n throw new Error(\"No token received from server\");\n }\n\n // Store token\n localStorage.setItem(this.config.tokenStorageKey!, token);\n\n // Get user info from token\n const user = await this.getUserFromToken(token);\n this.updateState({ user, status: \"authenticated\" });\n } catch (error) {\n this.updateState({ user: null, status: \"unauthenticated\" });\n throw error;\n }\n }\n\n async signOut(): Promise<void> {\n // Clear stored data\n localStorage.removeItem(this.config.tokenStorageKey!);\n localStorage.removeItem(this.config.userStorageKey!);\n\n this.updateState({ user: null, status: \"unauthenticated\" });\n }\n\n async getToken(): Promise<string | null> {\n const token = localStorage.getItem(this.config.tokenStorageKey!);\n if (!token || this.isTokenExpired(token)) {\n return null;\n }\n return token;\n }\n\n async refreshToken(): Promise<string | null> {\n // For JWT provider, there may be no refresh flow; return current token if valid\n return this.getToken();\n }\n\n async getUser(): Promise<User | null> {\n return this.currentState.user;\n }\n\n onAuthStateChange(callback: (state: AuthState) => void): () => void {\n this.listeners.push(callback);\n return () => {\n const index = this.listeners.indexOf(callback);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n async destroy(): Promise<void> {\n this.listeners = [];\n }\n\n private async getUserFromToken(token: string): Promise<User | null> {\n try {\n const payload = this.parseJWT(token);\n\n return {\n id: payload.sub,\n email: payload.email ?? \"\",\n name: payload.name,\n avatar: payload.picture,\n metadata: { provider: \"jwt\", subject: payload.sub },\n credits: { balance: 0, reserved: 0 },\n };\n } catch (error) {\n console.error(\"Failed to parse user from token:\", error);\n return null;\n }\n }\n\n /**\n * Parse a JWT and return its payload, with robust error handling.\n */\n private parseJWT(token: string): JWTPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\n \"Invalid JWT format: token must have exactly 3 parts separated by dots\"\n );\n }\n\n try {\n const payloadJson = this.base64UrlDecode(parts[1]);\n const payload = JSON.parse(payloadJson);\n\n if (typeof payload !== \"object\" || payload === null) {\n throw new Error(\"Invalid JWT payload: payload must be a JSON object\");\n }\n\n return payload;\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"Invalid JWT\")) {\n // Re-throw our own validation errors\n throw err;\n }\n throw new Error(\n \"Failed to decode or parse JWT payload: \" +\n (err instanceof Error ? err.message : String(err))\n );\n }\n }\n\n /**\n * Decodes a base64url-encoded string.\n */\n private base64UrlDecode(input: string): string {\n // Replace non-url compatible chars with base64 standard chars\n input = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\n // Pad out with standard base64 required padding characters\n const pad = input.length % 4;\n if (pad) {\n if (pad === 1) {\n throw new Error(\"Invalid base64url encoding: incorrect padding\");\n }\n input += \"=\".repeat(4 - pad);\n }\n\n try {\n return atob(input);\n } catch (e) {\n throw new Error(\n \"Invalid base64url encoding: \" +\n (e instanceof Error ? e.message : String(e))\n );\n }\n }\n\n private isTokenExpired(token: string): boolean {\n try {\n const payload = this.parseJWT(token);\n const now = Math.floor(Date.now() / 1000);\n return payload.exp < now;\n } catch {\n return true;\n }\n }\n\n private updateState(updates: Partial<AuthState>): void {\n this.currentState = { ...this.currentState, ...updates };\n this.listeners.forEach((listener) => listener(this.currentState));\n }\n}\n"],"mappings":";AAIA,SAAS,wBAAyC;AAG3C,IAAM,kBAAN,cAA8B,iBAAiB;AAAA,EAKpD,YAAY,QAAmB;AAC7B,UAAM,MAAM;AAJd,SAAQ,YAA4C,CAAC;AAKnD,SAAK,SAAS;AAAA,MACZ,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,GAAG;AAAA,IACL;AAEA,SAAK,eAAe;AAAA,MAClB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,KAAK,OAAO,KAAK,IAAI;AAAA,MAC7B,SAAS,KAAK,QAAQ,KAAK,IAAI;AAAA,MAC/B,UAAU,KAAK,SAAS,KAAK,IAAI;AAAA,MACjC,cAAc,KAAK,aAAa,KAAK,IAAI;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAM,aAA4B;AAEhC,UAAM,QAAQ,aAAa,QAAQ,KAAK,OAAO,eAAgB;AAC/D,QAAI,OAAO;AACT,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,iBAAiB,KAAK;AAC9C,YAAI,QAAQ,CAAC,KAAK,eAAe,KAAK,GAAG;AACvC,eAAK,YAAY,EAAE,MAAM,QAAQ,gBAAgB,CAAC;AAClD;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,KAAK,mCAAmC,KAAK;AAAA,MACvD;AAAA,IACF;AAEA,SAAK,YAAY,EAAE,MAAM,MAAM,QAAQ,kBAAkB,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,eAAmC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OACJ,SACe;AACf,UAAM,QAAS,SAAiB;AAChC,UAAM,WAAY,SAAiB;AACnC,SAAK,YAAY,EAAE,QAAQ,UAAU,CAAC;AAEtC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,MAAM,eAAe;AAAA,QAC/D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAI,KAAK,OAAO,YAAY,EAAE,YAAY,KAAK,OAAO,SAAS;AAAA,QACjE;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,MAC1C,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,0BAA0B,SAAS,UAAU,EAAE;AAAA,MACjE;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAM,EAAE,MAAM,IAAI;AAElB,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,MAAM,+BAA+B;AAAA,MACjD;AAGA,mBAAa,QAAQ,KAAK,OAAO,iBAAkB,KAAK;AAGxD,YAAM,OAAO,MAAM,KAAK,iBAAiB,KAAK;AAC9C,WAAK,YAAY,EAAE,MAAM,QAAQ,gBAAgB,CAAC;AAAA,IACpD,SAAS,OAAO;AACd,WAAK,YAAY,EAAE,MAAM,MAAM,QAAQ,kBAAkB,CAAC;AAC1D,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAE7B,iBAAa,WAAW,KAAK,OAAO,eAAgB;AACpD,iBAAa,WAAW,KAAK,OAAO,cAAe;AAEnD,SAAK,YAAY,EAAE,MAAM,MAAM,QAAQ,kBAAkB,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,WAAmC;AACvC,UAAM,QAAQ,aAAa,QAAQ,KAAK,OAAO,eAAgB;AAC/D,QAAI,CAAC,SAAS,KAAK,eAAe,KAAK,GAAG;AACxC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAuC;AAE3C,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,MAAM,UAAgC;AACpC,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA,EAEA,kBAAkB,UAAkD;AAClE,SAAK,UAAU,KAAK,QAAQ;AAC5B,WAAO,MAAM;AACX,YAAM,QAAQ,KAAK,UAAU,QAAQ,QAAQ;AAC7C,UAAI,QAAQ,IAAI;AACd,aAAK,UAAU,OAAO,OAAO,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,YAAY,CAAC;AAAA,EACpB;AAAA,EAEA,MAAc,iBAAiB,OAAqC;AAClE,QAAI;AACF,YAAM,UAAU,KAAK,SAAS,KAAK;AAEnC,aAAO;AAAA,QACL,IAAI,QAAQ;AAAA,QACZ,OAAO,QAAQ,SAAS;AAAA,QACxB,MAAM,QAAQ;AAAA,QACd,QAAQ,QAAQ;AAAA,QAChB,UAAU,EAAE,UAAU,OAAO,SAAS,QAAQ,IAAI;AAAA,QAClD,SAAS,EAAE,SAAS,GAAG,UAAU,EAAE;AAAA,MACrC;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,oCAAoC,KAAK;AACvD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,OAA2B;AAC1C,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,YAAM,cAAc,KAAK,gBAAgB,MAAM,CAAC,CAAC;AACjD,YAAM,UAAU,KAAK,MAAM,WAAW;AAEtC,UAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,cAAM,IAAI,MAAM,oDAAoD;AAAA,MACtE;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAI,eAAe,SAAS,IAAI,QAAQ,WAAW,aAAa,GAAG;AAEjE,cAAM;AAAA,MACR;AACA,YAAM,IAAI;AAAA,QACR,6CACG,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,OAAuB;AAE7C,YAAQ,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAGlD,UAAM,MAAM,MAAM,SAAS;AAC3B,QAAI,KAAK;AACP,UAAI,QAAQ,GAAG;AACb,cAAM,IAAI,MAAM,+CAA+C;AAAA,MACjE;AACA,eAAS,IAAI,OAAO,IAAI,GAAG;AAAA,IAC7B;AAEA,QAAI;AACF,aAAO,KAAK,KAAK;AAAA,IACnB,SAAS,GAAG;AACV,YAAM,IAAI;AAAA,QACR,kCACG,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eAAe,OAAwB;AAC7C,QAAI;AACF,YAAM,UAAU,KAAK,SAAS,KAAK;AACnC,YAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,aAAO,QAAQ,MAAM;AAAA,IACvB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,YAAY,SAAmC;AACrD,SAAK,eAAe,EAAE,GAAG,KAAK,cAAc,GAAG,QAAQ;AACvD,SAAK,UAAU,QAAQ,CAAC,aAAa,SAAS,KAAK,YAAY,CAAC;AAAA,EAClE;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@weirdfingers/boards-auth-jwt",
3
+ "version": "0.9.13",
4
+ "description": "JWT authentication provider for Boards",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "dev": "tsup --watch",
11
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
12
+ "lint": "eslint src --ext .ts,.tsx",
13
+ "test": "vitest --run"
14
+ },
15
+ "keywords": [
16
+ "boards",
17
+ "authentication",
18
+ "jwt",
19
+ "auth",
20
+ "react"
21
+ ],
22
+ "author": "WeirdFingers",
23
+ "license": "MIT",
24
+ "peerDependencies": {
25
+ "@weirdfingers/boards": "workspace:*",
26
+ "react": "^18.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/react": "^18.0.0",
30
+ "@typescript-eslint/eslint-plugin": "^8.42.0",
31
+ "@typescript-eslint/parser": "^8.42.0",
32
+ "@vitest/ui": "^3.2.4",
33
+ "@weirdfingers/boards": "workspace:*",
34
+ "eslint": "^8.0.0",
35
+ "jsdom": "^26.1.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.0.0",
38
+ "vitest": "^1.0.0"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "README.md"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/weirdfingers/boards.git",
47
+ "directory": "packages/auth-jwt"
48
+ }
49
+ }