supaapps-auth 2.0.0 → 3.0.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/dist/index.d.ts +88 -2
- package/dist/index.js +475 -19
- package/package.json +21 -15
- package/.eslintrc +0 -3
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/publish-to-npm.yml +0 -30
- package/.prettierrc +0 -7
- package/dist/AuthManager.d.ts +0 -47
- package/dist/AuthManager.js +0 -408
- package/dist/types.d.ts +0 -40
- package/dist/types.js +0 -24
- package/jest.config.js +0 -16
- package/src/AuthManager.ts +0 -542
- package/src/index.ts +0 -2
- package/src/types.ts +0 -45
- package/tests/AuthManager.test.ts +0 -153
- package/tsconfig.json +0 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
declare enum AuthEventType {
|
|
2
|
+
INITALIZED_IN = "initialized-logged-in",
|
|
3
|
+
INITALIZED_OUT = "initialized-logged-out",
|
|
4
|
+
USER_LOGGED_IN = "user-logged-in",
|
|
5
|
+
USER_LOGGED_OUT = "user-logged-out",
|
|
6
|
+
USER_UPDATED = "user-updated",
|
|
7
|
+
FAILED_MUST_LOGIN_CHECK = "failed-must-login",
|
|
8
|
+
REFRESH_FAILED = "refresh-failed"
|
|
9
|
+
}
|
|
10
|
+
interface UserTokenPayload {
|
|
11
|
+
id: number;
|
|
12
|
+
iss: string;
|
|
13
|
+
sub: number | string;
|
|
14
|
+
first_name: string;
|
|
15
|
+
last_name: string;
|
|
16
|
+
email: string;
|
|
17
|
+
aud: string;
|
|
18
|
+
iat: number;
|
|
19
|
+
exp: number;
|
|
20
|
+
scopes: string;
|
|
21
|
+
realm: string;
|
|
22
|
+
provider: Platforms;
|
|
23
|
+
}
|
|
24
|
+
interface AuthManagerEvent {
|
|
25
|
+
type: AuthEventType;
|
|
26
|
+
user?: UserTokenPayload;
|
|
27
|
+
}
|
|
28
|
+
interface PlatformCheckResponse {
|
|
29
|
+
platforms: Platforms[];
|
|
30
|
+
}
|
|
31
|
+
declare enum Platforms {
|
|
32
|
+
PASSWORD = "password",
|
|
33
|
+
GOOGLE = "google",
|
|
34
|
+
FACEBOOK = "facebook",
|
|
35
|
+
TWITTER = "twitter",
|
|
36
|
+
GITHUB = "github",
|
|
37
|
+
APPLE = "apple",
|
|
38
|
+
LINKEDIN = "linkedin",
|
|
39
|
+
MICROSOFT = "microsoft"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare class AuthManager {
|
|
43
|
+
private static instance;
|
|
44
|
+
private authServer;
|
|
45
|
+
private realmName;
|
|
46
|
+
private redirectUri;
|
|
47
|
+
private onStateChange;
|
|
48
|
+
private constructor();
|
|
49
|
+
static initialize(authServer: string, realmName: string, redirectUri: string, onStateChange: (event: AuthManagerEvent) => void): AuthManager;
|
|
50
|
+
static getInstance(): AuthManager;
|
|
51
|
+
private tokenToPayload;
|
|
52
|
+
private generatePKCEPair;
|
|
53
|
+
refreshAccessToken(isInitialization?: boolean): Promise<string>;
|
|
54
|
+
checkAccessToken(isInitilization?: boolean): Promise<string | null>;
|
|
55
|
+
private isTokenExpired;
|
|
56
|
+
mustBeLoggedIn(): Promise<void>;
|
|
57
|
+
getLoginWithGoogleUri(): string;
|
|
58
|
+
isLoggedIn(): Promise<boolean>;
|
|
59
|
+
getAccessToken(mustBeLoggedIn?: boolean): Promise<string | null>;
|
|
60
|
+
platformCheck(email: string): Promise<PlatformCheckResponse>;
|
|
61
|
+
verifyEmail(email: string, token: string): Promise<boolean>;
|
|
62
|
+
doPassReset(email: string, token: string, newPassword: string): Promise<boolean>;
|
|
63
|
+
changeEmail(email: string): Promise<boolean>;
|
|
64
|
+
initPasswordReset(email: string): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Updates user account fields. Only sends fields present in the update object.
|
|
67
|
+
* For password, expects: { old: string, new: string }
|
|
68
|
+
*/
|
|
69
|
+
updateAccount(update: {
|
|
70
|
+
firstName?: string;
|
|
71
|
+
lastName?: string;
|
|
72
|
+
email?: string;
|
|
73
|
+
password?: {
|
|
74
|
+
old: string;
|
|
75
|
+
new: string;
|
|
76
|
+
};
|
|
77
|
+
}): Promise<boolean>;
|
|
78
|
+
changePassword(oldPassword: string, newPassword: string, email: string): Promise<boolean>;
|
|
79
|
+
registerUsingEmail(firstName: string, lastName: string, email: string, password: string): Promise<void>;
|
|
80
|
+
private saveTokens;
|
|
81
|
+
loginUsingEmail(email: string, password: string): Promise<void>;
|
|
82
|
+
loginUsingPkce(code: string): Promise<void>;
|
|
83
|
+
logout(): Promise<void>;
|
|
84
|
+
static validateToken(authServer: string, bearerToken: string): Promise<UserTokenPayload>;
|
|
85
|
+
static resetInstance(): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { AuthEventType, AuthManager, type AuthManagerEvent, type PlatformCheckResponse, Platforms, type UserTokenPayload };
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,476 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var AuthEventType = /* @__PURE__ */ ((AuthEventType2) => {
|
|
3
|
+
AuthEventType2["INITALIZED_IN"] = "initialized-logged-in";
|
|
4
|
+
AuthEventType2["INITALIZED_OUT"] = "initialized-logged-out";
|
|
5
|
+
AuthEventType2["USER_LOGGED_IN"] = "user-logged-in";
|
|
6
|
+
AuthEventType2["USER_LOGGED_OUT"] = "user-logged-out";
|
|
7
|
+
AuthEventType2["USER_UPDATED"] = "user-updated";
|
|
8
|
+
AuthEventType2["FAILED_MUST_LOGIN_CHECK"] = "failed-must-login";
|
|
9
|
+
AuthEventType2["REFRESH_FAILED"] = "refresh-failed";
|
|
10
|
+
return AuthEventType2;
|
|
11
|
+
})(AuthEventType || {});
|
|
12
|
+
var Platforms = /* @__PURE__ */ ((Platforms2) => {
|
|
13
|
+
Platforms2["PASSWORD"] = "password";
|
|
14
|
+
Platforms2["GOOGLE"] = "google";
|
|
15
|
+
Platforms2["FACEBOOK"] = "facebook";
|
|
16
|
+
Platforms2["TWITTER"] = "twitter";
|
|
17
|
+
Platforms2["GITHUB"] = "github";
|
|
18
|
+
Platforms2["APPLE"] = "apple";
|
|
19
|
+
Platforms2["LINKEDIN"] = "linkedin";
|
|
20
|
+
Platforms2["MICROSOFT"] = "microsoft";
|
|
21
|
+
return Platforms2;
|
|
22
|
+
})(Platforms || {});
|
|
23
|
+
|
|
24
|
+
// src/AuthManager.ts
|
|
25
|
+
import axios from "axios";
|
|
26
|
+
import {
|
|
27
|
+
jwtVerify
|
|
28
|
+
} from "jose";
|
|
29
|
+
import { jwtDecode } from "jwt-decode";
|
|
30
|
+
|
|
31
|
+
// src/utils/pkce.ts
|
|
32
|
+
import { sha256 } from "js-sha256";
|
|
33
|
+
var nodeCrypto;
|
|
34
|
+
if (typeof window === "undefined") {
|
|
35
|
+
nodeCrypto = await import("crypto");
|
|
36
|
+
}
|
|
37
|
+
function toBase64Url(bytes) {
|
|
38
|
+
let base64;
|
|
39
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(bytes)) {
|
|
40
|
+
base64 = bytes.toString("base64");
|
|
41
|
+
} else {
|
|
42
|
+
let binary = "";
|
|
43
|
+
const chunkSize = 32768;
|
|
44
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
45
|
+
const chunk = bytes.subarray(i, i + chunkSize);
|
|
46
|
+
binary += String.fromCharCode(...chunk);
|
|
47
|
+
}
|
|
48
|
+
base64 = btoa(binary);
|
|
49
|
+
}
|
|
50
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
51
|
+
}
|
|
52
|
+
function generateCodeVerifier(length = 32) {
|
|
53
|
+
let bytes;
|
|
54
|
+
if (typeof window === "undefined") {
|
|
55
|
+
bytes = nodeCrypto.randomBytes(length);
|
|
56
|
+
} else {
|
|
57
|
+
bytes = new Uint8Array(length);
|
|
58
|
+
window.crypto.getRandomValues(bytes);
|
|
59
|
+
}
|
|
60
|
+
return toBase64Url(bytes);
|
|
61
|
+
}
|
|
62
|
+
function generateCodeChallenge(verifier) {
|
|
63
|
+
const hashBytes = new Uint8Array(sha256.array(verifier));
|
|
64
|
+
return toBase64Url(hashBytes);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/AuthManager.ts
|
|
68
|
+
var AuthManager = class _AuthManager {
|
|
69
|
+
static instance = null;
|
|
70
|
+
authServer;
|
|
71
|
+
realmName;
|
|
72
|
+
redirectUri;
|
|
73
|
+
onStateChange;
|
|
74
|
+
constructor(authServer, realmName, redirectUri, onStateChange) {
|
|
75
|
+
this.authServer = authServer;
|
|
76
|
+
this.realmName = realmName;
|
|
77
|
+
this.redirectUri = redirectUri;
|
|
78
|
+
this.onStateChange = onStateChange;
|
|
79
|
+
_AuthManager.instance = this;
|
|
80
|
+
}
|
|
81
|
+
static initialize(authServer, realmName, redirectUri, onStateChange) {
|
|
82
|
+
if (!_AuthManager.instance) {
|
|
83
|
+
_AuthManager.instance = new _AuthManager(
|
|
84
|
+
authServer,
|
|
85
|
+
realmName,
|
|
86
|
+
redirectUri,
|
|
87
|
+
onStateChange
|
|
88
|
+
);
|
|
89
|
+
_AuthManager.instance.checkAccessToken(true).then((token) => {
|
|
90
|
+
onStateChange({
|
|
91
|
+
type: "initialized-logged-in" /* INITALIZED_IN */,
|
|
92
|
+
user: _AuthManager.instance?.tokenToPayload(token)
|
|
93
|
+
});
|
|
94
|
+
}).catch(() => {
|
|
95
|
+
onStateChange({ type: "initialized-logged-out" /* INITALIZED_OUT */ });
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return _AuthManager.instance;
|
|
99
|
+
}
|
|
100
|
+
static getInstance() {
|
|
101
|
+
if (!_AuthManager.instance) {
|
|
102
|
+
throw new Error("AuthManager not initialized");
|
|
103
|
+
}
|
|
104
|
+
return _AuthManager.instance;
|
|
105
|
+
}
|
|
106
|
+
tokenToPayload(token) {
|
|
107
|
+
return JSON.parse(atob(token.split(".")[1]));
|
|
108
|
+
}
|
|
109
|
+
generatePKCEPair() {
|
|
110
|
+
const verifier = localStorage.getItem("codeVerifier") ?? generateCodeVerifier();
|
|
111
|
+
const challenge = localStorage.getItem("codeChallenge") ?? generateCodeChallenge(verifier);
|
|
112
|
+
localStorage.setItem("codeVerifier", verifier);
|
|
113
|
+
localStorage.setItem("codeChallenge", challenge);
|
|
114
|
+
return { verifier, challenge };
|
|
115
|
+
}
|
|
116
|
+
async refreshAccessToken(isInitialization = false) {
|
|
117
|
+
try {
|
|
118
|
+
const refreshToken = localStorage.getItem("refresh_token");
|
|
119
|
+
if (!refreshToken) {
|
|
120
|
+
throw new Error("No refresh token found");
|
|
121
|
+
}
|
|
122
|
+
const response = await axios.post(
|
|
123
|
+
`${this.authServer}auth/refresh`,
|
|
124
|
+
{
|
|
125
|
+
refresh_token: refreshToken
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
this.saveTokens(response, true);
|
|
129
|
+
return response.data.access_token;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`Refresh token error, logging out: ${error}`);
|
|
132
|
+
localStorage.removeItem("access_token");
|
|
133
|
+
localStorage.removeItem("refresh_token");
|
|
134
|
+
if (!isInitialization) {
|
|
135
|
+
this.onStateChange({ type: "refresh-failed" /* REFRESH_FAILED */ });
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async checkAccessToken(isInitilization = false) {
|
|
141
|
+
const accessToken = localStorage.getItem("access_token");
|
|
142
|
+
if (accessToken && this.isTokenExpired(accessToken)) {
|
|
143
|
+
return this.refreshAccessToken(isInitilization);
|
|
144
|
+
}
|
|
145
|
+
return accessToken;
|
|
146
|
+
}
|
|
147
|
+
isTokenExpired(token) {
|
|
148
|
+
const decoded = this.tokenToPayload(token);
|
|
149
|
+
return decoded.exp < Date.now() / 1e3;
|
|
150
|
+
}
|
|
151
|
+
async mustBeLoggedIn() {
|
|
152
|
+
if (!await this.isLoggedIn()) {
|
|
153
|
+
this.onStateChange({
|
|
154
|
+
type: "failed-must-login" /* FAILED_MUST_LOGIN_CHECK */
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
getLoginWithGoogleUri() {
|
|
159
|
+
const { challenge } = this.generatePKCEPair();
|
|
160
|
+
return `${this.authServer}auth/login_with_google?realm_name=${this.realmName}&redirect_uri=${encodeURIComponent(this.redirectUri)}&code_challenge=${challenge}&code_challenge_method=S256`;
|
|
161
|
+
}
|
|
162
|
+
async isLoggedIn() {
|
|
163
|
+
const accessToken = localStorage.getItem("access_token");
|
|
164
|
+
const refreshToken = localStorage.getItem("refresh_token");
|
|
165
|
+
if (!accessToken || !refreshToken) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (this.isTokenExpired(accessToken)) {
|
|
169
|
+
try {
|
|
170
|
+
await this.refreshAccessToken();
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
async getAccessToken(mustBeLoggedIn = false) {
|
|
179
|
+
try {
|
|
180
|
+
return await this.checkAccessToken();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (mustBeLoggedIn) {
|
|
183
|
+
this.onStateChange({
|
|
184
|
+
type: "failed-must-login" /* FAILED_MUST_LOGIN_CHECK */
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async platformCheck(email) {
|
|
191
|
+
const response = await axios.post(
|
|
192
|
+
`${this.authServer}auth/email/platform_check`,
|
|
193
|
+
{
|
|
194
|
+
realm_name: this.realmName,
|
|
195
|
+
email
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
if (response.data.error || response.data.errors) {
|
|
199
|
+
throw new Error(response.data.error || response.data.message);
|
|
200
|
+
}
|
|
201
|
+
return response.status === 200 ? response.data : { platforms: [] };
|
|
202
|
+
}
|
|
203
|
+
async verifyEmail(email, token) {
|
|
204
|
+
const response = await axios.post(
|
|
205
|
+
`${this.authServer}auth/email/verify`,
|
|
206
|
+
{
|
|
207
|
+
realm_name: this.realmName,
|
|
208
|
+
email,
|
|
209
|
+
token
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
if (response.data.error || response.data.errors) {
|
|
213
|
+
throw new Error(response.data.error || response.data.message);
|
|
214
|
+
}
|
|
215
|
+
return response.status === 200;
|
|
216
|
+
}
|
|
217
|
+
async doPassReset(email, token, newPassword) {
|
|
218
|
+
const response = await axios.post(
|
|
219
|
+
`${this.authServer}auth/email/do_pass_reset`,
|
|
220
|
+
{
|
|
221
|
+
realm_name: this.realmName,
|
|
222
|
+
email,
|
|
223
|
+
token,
|
|
224
|
+
new_password: newPassword
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
if (response.data.error || response.data.errors) {
|
|
228
|
+
throw new Error(response.data.error || response.data.message);
|
|
229
|
+
}
|
|
230
|
+
return response.status === 200;
|
|
231
|
+
}
|
|
232
|
+
async changeEmail(email) {
|
|
233
|
+
const accessToken = localStorage.getItem("access_token");
|
|
234
|
+
if (!accessToken) {
|
|
235
|
+
throw new Error("Access token not found");
|
|
236
|
+
}
|
|
237
|
+
const response = await axios.post(
|
|
238
|
+
`${this.authServer}auth/email/change_email`,
|
|
239
|
+
{
|
|
240
|
+
realm_name: this.realmName,
|
|
241
|
+
email
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
if (response.data.error || response.data.errors) {
|
|
248
|
+
throw new Error(response.data.error || response.data.message);
|
|
249
|
+
}
|
|
250
|
+
return response.status === 200;
|
|
251
|
+
}
|
|
252
|
+
async initPasswordReset(email) {
|
|
253
|
+
const response = await axios.post(
|
|
254
|
+
`${this.authServer}auth/email/init_pass_reset`,
|
|
255
|
+
{
|
|
256
|
+
realm_name: this.realmName,
|
|
257
|
+
email
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
if (response.data.error || response.data.errors) {
|
|
261
|
+
throw new Error(response.data.error || response.data.message);
|
|
262
|
+
}
|
|
263
|
+
return response.status === 200 || response.status === 201;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Updates user account fields. Only sends fields present in the update object.
|
|
267
|
+
* For password, expects: { old: string, new: string }
|
|
268
|
+
*/
|
|
269
|
+
async updateAccount(update) {
|
|
270
|
+
const accessToken = localStorage.getItem("access_token");
|
|
271
|
+
if (!accessToken) {
|
|
272
|
+
throw new Error("Access token not found");
|
|
273
|
+
}
|
|
274
|
+
if (update.firstName || update.lastName) {
|
|
275
|
+
const response = await axios.post(
|
|
276
|
+
`${this.authServer}auth/email/update_profile`,
|
|
277
|
+
{
|
|
278
|
+
realm_name: this.realmName,
|
|
279
|
+
...update.firstName && { first_name: update.firstName },
|
|
280
|
+
...update.lastName && { last_name: update.lastName }
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
if (response.data.error || response.data.errors) {
|
|
287
|
+
throw new Error(response.data.error || response.data.message);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (update.email) {
|
|
291
|
+
const response = await axios.post(
|
|
292
|
+
`${this.authServer}auth/email/change_email`,
|
|
293
|
+
{
|
|
294
|
+
realm_name: this.realmName,
|
|
295
|
+
email: update.email
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
if (response.data.error || response.data.errors) {
|
|
302
|
+
throw new Error(response.data.error || response.data.message);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (update.password && update.email) {
|
|
306
|
+
const response = await axios.post(
|
|
307
|
+
`${this.authServer}auth/email/change_pass`,
|
|
308
|
+
{
|
|
309
|
+
realm_name: this.realmName,
|
|
310
|
+
email: update.email,
|
|
311
|
+
old_password: update.password.old,
|
|
312
|
+
new_password: update.password.new
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
if (response.data.error || response.data.errors) {
|
|
319
|
+
throw new Error(response.data.error || response.data.message);
|
|
320
|
+
}
|
|
321
|
+
} else if (update.password && !update.email) {
|
|
322
|
+
throw new Error("Email is required to change password");
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
async changePassword(oldPassword, newPassword, email) {
|
|
327
|
+
const accessToken = localStorage.getItem("access_token");
|
|
328
|
+
if (!accessToken) {
|
|
329
|
+
throw new Error("Access token not found");
|
|
330
|
+
}
|
|
331
|
+
const response = await axios.post(
|
|
332
|
+
`${this.authServer}auth/email/change_pass`,
|
|
333
|
+
{
|
|
334
|
+
realm_name: this.realmName,
|
|
335
|
+
email,
|
|
336
|
+
old_password: oldPassword,
|
|
337
|
+
new_password: newPassword
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
if (response.data.error || response.data.errors) {
|
|
344
|
+
throw new Error(response.data.error || response.data.message);
|
|
345
|
+
}
|
|
346
|
+
return response.status === 200;
|
|
347
|
+
}
|
|
348
|
+
async registerUsingEmail(firstName, lastName, email, password) {
|
|
349
|
+
const response = await axios.post(
|
|
350
|
+
`${this.authServer}auth/email/register`,
|
|
351
|
+
{
|
|
352
|
+
realm_name: this.realmName,
|
|
353
|
+
first_name: firstName,
|
|
354
|
+
last_name: lastName,
|
|
355
|
+
email,
|
|
356
|
+
password
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
if (response.data.message || response.data.error) {
|
|
360
|
+
throw new Error(response.data.message || response.data.error);
|
|
361
|
+
}
|
|
362
|
+
if (!response.data.access_token) {
|
|
363
|
+
throw new Error("Something went wrong");
|
|
364
|
+
}
|
|
365
|
+
this.saveTokens(response, false);
|
|
366
|
+
}
|
|
367
|
+
saveTokens(response, byRefresh) {
|
|
368
|
+
localStorage.setItem("access_token", response.data.access_token);
|
|
369
|
+
localStorage.setItem(
|
|
370
|
+
"refresh_token",
|
|
371
|
+
response.data.refresh_token
|
|
372
|
+
);
|
|
373
|
+
this.onStateChange({
|
|
374
|
+
type: byRefresh ? "user-updated" /* USER_UPDATED */ : "user-logged-in" /* USER_LOGGED_IN */,
|
|
375
|
+
user: this.tokenToPayload(response.data.access_token)
|
|
376
|
+
});
|
|
377
|
+
const user = this.tokenToPayload(response.data.access_token);
|
|
378
|
+
localStorage.setItem("user", JSON.stringify(user));
|
|
379
|
+
}
|
|
380
|
+
async loginUsingEmail(email, password) {
|
|
381
|
+
const response = await axios.post(
|
|
382
|
+
`${this.authServer}auth/email/login`,
|
|
383
|
+
{
|
|
384
|
+
realm_name: this.realmName,
|
|
385
|
+
email,
|
|
386
|
+
password
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
if (response.data.message || response.data.error) {
|
|
390
|
+
throw new Error(response.data.message || response.data.error);
|
|
391
|
+
}
|
|
392
|
+
this.saveTokens(response, false);
|
|
393
|
+
}
|
|
394
|
+
async loginUsingPkce(code) {
|
|
395
|
+
try {
|
|
396
|
+
const codeVerifier = localStorage.getItem("codeVerifier");
|
|
397
|
+
if (!codeVerifier) {
|
|
398
|
+
throw new Error("Code verifier not found");
|
|
399
|
+
}
|
|
400
|
+
const response = await axios.post(
|
|
401
|
+
`${this.authServer}auth/pkce_exchange`,
|
|
402
|
+
{
|
|
403
|
+
realm_name: this.realmName,
|
|
404
|
+
code,
|
|
405
|
+
redirect_uri: this.redirectUri,
|
|
406
|
+
code_verifier: codeVerifier
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
this.saveTokens(response, false);
|
|
410
|
+
} finally {
|
|
411
|
+
localStorage.removeItem("codeVerifier");
|
|
412
|
+
localStorage.removeItem("codeChallenge");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async logout() {
|
|
416
|
+
try {
|
|
417
|
+
const accessToken = localStorage.getItem("access_token");
|
|
418
|
+
if (!accessToken) {
|
|
419
|
+
throw new Error("Access token not found");
|
|
420
|
+
}
|
|
421
|
+
await axios.post(
|
|
422
|
+
`${this.authServer}auth/logout`,
|
|
423
|
+
{},
|
|
424
|
+
{
|
|
425
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
} finally {
|
|
429
|
+
localStorage.removeItem("access_token");
|
|
430
|
+
localStorage.removeItem("refresh_token");
|
|
431
|
+
this.onStateChange({ type: "user-logged-out" /* USER_LOGGED_OUT */ });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
static async validateToken(authServer, bearerToken) {
|
|
435
|
+
const decodedToken = jwtDecode(bearerToken);
|
|
436
|
+
if (!decodedToken) {
|
|
437
|
+
throw new Error("Not a valid jwt token");
|
|
438
|
+
}
|
|
439
|
+
const userToken = {
|
|
440
|
+
id: decodedToken.id,
|
|
441
|
+
iss: decodedToken.iss,
|
|
442
|
+
sub: typeof decodedToken.sub === "string" ? parseInt(decodedToken.sub) : decodedToken.sub,
|
|
443
|
+
first_name: decodedToken.first_name,
|
|
444
|
+
last_name: decodedToken.last_name,
|
|
445
|
+
email: decodedToken.email,
|
|
446
|
+
aud: decodedToken.aud,
|
|
447
|
+
iat: decodedToken.iat,
|
|
448
|
+
exp: decodedToken.exp,
|
|
449
|
+
scopes: decodedToken.scopes,
|
|
450
|
+
realm: decodedToken.realm,
|
|
451
|
+
provider: decodedToken.provider
|
|
452
|
+
};
|
|
453
|
+
const { data: publicKey } = await axios.get(
|
|
454
|
+
`${authServer}public/public_key`
|
|
455
|
+
);
|
|
456
|
+
const { data: algo } = await axios.get(
|
|
457
|
+
`${authServer}public/algo`
|
|
458
|
+
);
|
|
459
|
+
jwtVerify(bearerToken, publicKey, { algorithms: [algo] });
|
|
460
|
+
const { data: revokedIds } = await axios.get(
|
|
461
|
+
`${authServer}public/revoked_ids`
|
|
462
|
+
);
|
|
463
|
+
if (revokedIds.includes(decodedToken.id)) {
|
|
464
|
+
throw new Error("Token is revoked");
|
|
465
|
+
}
|
|
466
|
+
return userToken;
|
|
467
|
+
}
|
|
468
|
+
static resetInstance() {
|
|
469
|
+
_AuthManager.instance = null;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
export {
|
|
473
|
+
AuthEventType,
|
|
474
|
+
AuthManager,
|
|
475
|
+
Platforms
|
|
15
476
|
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.AuthManager = void 0;
|
|
18
|
-
__exportStar(require("./types"), exports);
|
|
19
|
-
var AuthManager_1 = require("./AuthManager");
|
|
20
|
-
Object.defineProperty(exports, "AuthManager", { enumerable: true, get: function () { return AuthManager_1.AuthManager; } });
|
package/package.json
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supaapps-auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"scripts": {
|
|
8
|
-
"test": "
|
|
9
|
-
"build": "
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
16
|
+
"watch": "tsup src/index.ts --format esm --dts --watch",
|
|
10
17
|
"lint": "eslint src/ --ext .ts,.tsx"
|
|
11
18
|
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
12
22
|
"author": "",
|
|
13
23
|
"license": "MIT",
|
|
14
24
|
"dependencies": {
|
|
15
25
|
"axios": "^1.6.7",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
26
|
+
"jose": "^6.1.0",
|
|
27
|
+
"js-sha256": "^0.11.1",
|
|
28
|
+
"jwt-decode": "^4.0.0"
|
|
18
29
|
},
|
|
19
30
|
"devDependencies": {
|
|
20
31
|
"@next/eslint-plugin-next": "^13.5.6",
|
|
21
32
|
"@types/axios": "^0.14.0",
|
|
22
|
-
"@types/jest": "^29.5.12",
|
|
23
|
-
"@types/jsonwebtoken": "^9.0.6",
|
|
24
|
-
"@types/node": "^20.12.12",
|
|
25
33
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
26
34
|
"@typescript-eslint/parser": "^6.21.0",
|
|
27
35
|
"axios-mock-adapter": "^1.22.0",
|
|
@@ -32,11 +40,9 @@
|
|
|
32
40
|
"eslint-config-prettier": "^9.1.0",
|
|
33
41
|
"eslint-config-supaapps": "^1.1.0",
|
|
34
42
|
"eslint-plugin-import": "^2.29.1",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"typescript": "^5.3.3",
|
|
40
|
-
"undefined": "^0.1.0"
|
|
43
|
+
"jsdom": "^27.2.0",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vitest": "^4.0.10"
|
|
41
47
|
}
|
|
42
48
|
}
|
package/.eslintrc
DELETED
package/.github/workflows/ci.yml
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Node.js CI
|
|
2
|
-
|
|
3
|
-
on: [push, pull_request]
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
jobs:
|
|
7
|
-
build:
|
|
8
|
-
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
|
|
11
|
-
strategy:
|
|
12
|
-
matrix:
|
|
13
|
-
node-version: [18.x, 20.x]
|
|
14
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
15
|
-
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v3
|
|
18
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
19
|
-
uses: actions/setup-node@v3
|
|
20
|
-
with:
|
|
21
|
-
node-version: ${{ matrix.node-version }}
|
|
22
|
-
cache: 'npm'
|
|
23
|
-
- run: npm ci
|
|
24
|
-
- run: npm run lint
|
|
25
|
-
- run: npm run build --if-present
|
|
26
|
-
- run: npm run test
|
|
27
|
-
|
|
28
|
-
- name: Upload coverage reports to Codecov
|
|
29
|
-
uses: codecov/codecov-action@v4.0.1
|
|
30
|
-
with:
|
|
31
|
-
token: ${{ secrets.CODECOV_TOKEN }}
|
|
32
|
-
slug: supaapps/supaapps-auth
|