@plitzi/sdk-auth 0.32.25 → 0.33.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/CHANGELOG.md +23 -0
- package/dist/AuthContextProvider.d.ts +2 -5
- package/dist/AuthContextProvider.mjs +74 -70
- package/dist/AuthManager.d.ts +58 -20
- package/dist/AuthManager.mjs +42 -17
- package/dist/AuthProvider.d.ts +170 -33
- package/dist/AuthProvider.mjs +250 -60
- package/dist/helpers/SessionStore.d.ts +34 -0
- package/dist/helpers/SessionStore.mjs +45 -0
- package/dist/helpers/sessionHint.d.ts +15 -0
- package/dist/helpers/sessionHint.mjs +17 -0
- package/dist/helpers/tokenClaims.d.ts +11 -0
- package/dist/helpers/tokenClaims.mjs +19 -0
- package/dist/hooks/useAuth/useAuth.d.ts +40 -12
- package/dist/hooks/useAuth/useAuth.mjs +35 -48
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +8 -5
- package/dist/providers/BasicAuthProvider.d.ts +41 -28
- package/dist/providers/BasicAuthProvider.mjs +151 -71
- package/dist/types.d.ts +6 -0
- package/dist/types.mjs +0 -0
- package/package.json +22 -10
- package/dist/providers/Auth0Provider.d.ts +0 -22
- package/dist/providers/Auth0Provider.mjs +0 -25
package/dist/AuthProvider.mjs
CHANGED
|
@@ -1,90 +1,280 @@
|
|
|
1
|
+
import { readSessionHint as e } from "./helpers/sessionHint.mjs";
|
|
2
|
+
import t from "./helpers/SessionStore.mjs";
|
|
3
|
+
import { nowInSeconds as n, toSeconds as r, tokenExpiresAt as i } from "./helpers/tokenClaims.mjs";
|
|
4
|
+
import { onAuthFailure as a, sameRegistrableDomain as o } from "@plitzi/sdk-shared/auth";
|
|
1
5
|
//#region src/AuthProvider.ts
|
|
2
|
-
var
|
|
3
|
-
cache;
|
|
4
|
-
enableRefresh = !1;
|
|
5
|
-
tokenStorage = "localStorage";
|
|
6
|
+
var s = 60, c = 300, l = 2147483647, u = class {
|
|
6
7
|
state = "init";
|
|
8
|
+
session = { validatedAt: 0 };
|
|
9
|
+
store;
|
|
10
|
+
gate;
|
|
11
|
+
hintCookie;
|
|
12
|
+
revalidateSeconds;
|
|
7
13
|
listeners = /* @__PURE__ */ new Set();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (n in e) return Reflect.get(e, n, r);
|
|
16
|
-
if (!(e.cache && n in e.cache && n === "token" && t === "") && e.cache && n in e.cache) return e.cache[n];
|
|
17
|
-
} });
|
|
14
|
+
renewal;
|
|
15
|
+
renewalTimer;
|
|
16
|
+
detachListeners;
|
|
17
|
+
offline = !1;
|
|
18
|
+
spaceKey;
|
|
19
|
+
constructor({ spaceKey: e = "", tokenStorage: n = "localStorage", sessionHintCookie: r, sessionGate: i = "optimistic", sessionRevalidateSeconds: a = c, storageKey: o = "plitzi_auth_session" } = {}) {
|
|
20
|
+
this.spaceKey = e, this.store = new t(n, o), this.gate = i, this.hintCookie = r, this.revalidateSeconds = a;
|
|
18
21
|
}
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
get endpoints() {
|
|
23
|
+
return [];
|
|
21
24
|
}
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
requestExchange() {
|
|
26
|
+
return Promise.resolve(void 0);
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
consumeRedirect() {
|
|
29
|
+
return Promise.resolve(void 0);
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
get user() {
|
|
32
|
+
return this.session.user;
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
return
|
|
34
|
+
get token() {
|
|
35
|
+
return this.session.token;
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
getState() {
|
|
38
|
+
return this.state;
|
|
39
|
+
}
|
|
40
|
+
can(e) {
|
|
41
|
+
let t = this.session.user?.permissions;
|
|
42
|
+
return Array.isArray(t) && t.includes(e);
|
|
43
|
+
}
|
|
44
|
+
peekState() {
|
|
45
|
+
return this.peek().state;
|
|
46
|
+
}
|
|
47
|
+
peek() {
|
|
48
|
+
let t = this.store.read(), a = e(this.hintCookie);
|
|
49
|
+
if (this.hintCookie && !a) return { state: "guest" };
|
|
50
|
+
let o = t?.token, c = r(o?.expiresAt) ?? i(o?.accessToken);
|
|
51
|
+
return o?.accessToken && (c === void 0 || c - s > n()) && t?.user ? {
|
|
52
|
+
state: "authenticated",
|
|
53
|
+
user: t.user,
|
|
54
|
+
token: t.token
|
|
55
|
+
} : o?.accessToken || o?.refreshToken || a ? {
|
|
56
|
+
state: "init",
|
|
57
|
+
user: t?.user,
|
|
58
|
+
token: t?.token
|
|
59
|
+
} : { state: "guest" };
|
|
60
|
+
}
|
|
61
|
+
async init(e = {}) {
|
|
62
|
+
this.attach(), await this.decide(e), this.settle();
|
|
63
|
+
}
|
|
64
|
+
settle() {
|
|
65
|
+
(this.state === "init" || this.state === "initLoading") && this.setState(this.session.user || this.hasLiveToken() ? "authenticated" : "guest", "settled");
|
|
66
|
+
}
|
|
67
|
+
async decide(t) {
|
|
68
|
+
if (t.skipAuth) {
|
|
69
|
+
this.setState("guest", "skip-auth");
|
|
37
70
|
return;
|
|
38
71
|
}
|
|
39
|
-
|
|
72
|
+
let n = await this.consumeRedirect();
|
|
73
|
+
if (n) {
|
|
74
|
+
n.ok ? (this.adopt(n), await this.handOffToServer(), this.emit({
|
|
75
|
+
type: "login",
|
|
76
|
+
token: this.session.token
|
|
77
|
+
})) : this.endSession(n.reason);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (t.user) {
|
|
81
|
+
this.adopt({
|
|
82
|
+
ok: !0,
|
|
83
|
+
user: t.user,
|
|
84
|
+
token: t.accessToken ? {
|
|
85
|
+
accessToken: t.accessToken,
|
|
86
|
+
expiresAt: t.expiresAt ?? null,
|
|
87
|
+
refreshToken: null
|
|
88
|
+
} : this.session.token
|
|
89
|
+
}, !0, "bootstrap");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let r = this.store.read(), i = e(this.hintCookie);
|
|
93
|
+
if (this.session = {
|
|
94
|
+
user: r?.user,
|
|
95
|
+
token: r?.token,
|
|
96
|
+
validatedAt: r?.validatedAt ?? 0
|
|
97
|
+
}, this.hintCookie && !i) {
|
|
98
|
+
this.endSession(void 0, "hint-missing");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (this.hasLiveToken() && this.session.user) {
|
|
102
|
+
this.setState("authenticated", "restored"), this.scheduleRenewal();
|
|
103
|
+
let e = this.isFresh() && this.gate !== "strict" ? void 0 : this.revalidate(!0);
|
|
104
|
+
this.gate === "strict" && await e;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!this.hasEvidence()) {
|
|
108
|
+
this.endSession(void 0, "no-evidence");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (this.setState("initLoading", "no-evidence"), this.hasLiveToken() && this.capabilities.identity) {
|
|
112
|
+
await this.loadIdentity();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (this.capabilities.renew) {
|
|
116
|
+
await this.renew();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (this.capabilities.identity) {
|
|
120
|
+
await this.loadIdentity();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.endSession();
|
|
124
|
+
}
|
|
125
|
+
dispose() {
|
|
126
|
+
clearTimeout(this.renewalTimer), this.renewalTimer = void 0, this.detachListeners?.(), this.detachListeners = void 0;
|
|
127
|
+
}
|
|
128
|
+
async login(e) {
|
|
129
|
+
this.setState("authenticating", "authenticating");
|
|
130
|
+
let t = await this.requestLogin(e);
|
|
131
|
+
if (!t.ok) {
|
|
132
|
+
this.endSession(t.reason);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (this.adopt(t), await this.handOffToServer(), this.state !== "guest") return !this.session.user && this.capabilities.identity && await this.loadIdentity(), this.emit({
|
|
136
|
+
type: "login",
|
|
137
|
+
token: this.session.token
|
|
138
|
+
}), this.session.token;
|
|
139
|
+
}
|
|
140
|
+
async handOffToServer() {
|
|
141
|
+
let e = await this.requestExchange();
|
|
142
|
+
if (e) {
|
|
143
|
+
if (e.ok) {
|
|
144
|
+
this.adopt(e);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (e.reason === "network") {
|
|
148
|
+
this.offline = !0;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.endSession(e.reason);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async refresh() {
|
|
155
|
+
return await this.renew(), this.session.token;
|
|
40
156
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
157
|
+
async revalidate(e = !1) {
|
|
158
|
+
return this.hasEvidence() ? !e && this.isFresh() && this.hasLiveToken() ? this.state === "authenticated" : this.hasLiveToken() && this.capabilities.identity ? this.loadIdentity() : this.capabilities.renew ? this.renew() : this.capabilities.identity ? this.loadIdentity() : (this.hasLiveToken() || this.endSession("expired"), this.state === "authenticated") : (this.state !== "guest" && this.endSession(), !1);
|
|
159
|
+
}
|
|
160
|
+
invalidate(e = "expired") {
|
|
161
|
+
if (e === "network") {
|
|
162
|
+
this.offline = !0;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (e === "expired" && this.capabilities.renew) {
|
|
166
|
+
this.renew();
|
|
44
167
|
return;
|
|
45
168
|
}
|
|
46
|
-
|
|
169
|
+
this.endSession(e);
|
|
170
|
+
}
|
|
171
|
+
async logout() {
|
|
172
|
+
await this.requestLogout(), this.endSession(), this.emit({ type: "logout" });
|
|
47
173
|
}
|
|
48
174
|
on(e) {
|
|
49
|
-
|
|
175
|
+
let t = Array.isArray(e) ? e : [e];
|
|
176
|
+
return t.forEach((e) => this.listeners.add(e)), () => t.forEach((e) => this.listeners.delete(e));
|
|
50
177
|
}
|
|
51
178
|
emit(e) {
|
|
52
179
|
for (let t of this.listeners) t(e);
|
|
53
180
|
}
|
|
54
|
-
setState(e) {
|
|
181
|
+
setState(e, t) {
|
|
55
182
|
this.state !== e && (this.state = e, this.emit({
|
|
56
183
|
type: "state",
|
|
57
|
-
state: e
|
|
184
|
+
state: e,
|
|
185
|
+
reason: t
|
|
58
186
|
}));
|
|
59
187
|
}
|
|
60
|
-
|
|
61
|
-
return this.
|
|
188
|
+
async renew() {
|
|
189
|
+
return this.renewal ??= this.performRenewal().finally(() => {
|
|
190
|
+
this.renewal = void 0;
|
|
191
|
+
}), this.renewal;
|
|
62
192
|
}
|
|
63
|
-
async
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
193
|
+
async performRenewal() {
|
|
194
|
+
let e = await this.requestRenewal(this.session.token?.refreshToken ?? void 0);
|
|
195
|
+
return e.ok ? (this.adopt(e), !this.session.user && this.capabilities.identity ? this.loadIdentity() : this.state === "authenticated") : (this.handleFailure(e.reason), !1);
|
|
196
|
+
}
|
|
197
|
+
async loadIdentity() {
|
|
198
|
+
let e = await this.requestIdentity();
|
|
199
|
+
return e.ok ? (this.adopt(e), this.state === "authenticated") : e.reason === "expired" && this.capabilities.renew && !this.renewal ? this.renew() : (this.handleFailure(e.reason), !1);
|
|
200
|
+
}
|
|
201
|
+
handleFailure(e) {
|
|
202
|
+
if (e === "network") {
|
|
203
|
+
this.offline = !0;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.endSession(e);
|
|
207
|
+
}
|
|
208
|
+
adopt(e, t = !0, r = "granted") {
|
|
209
|
+
this.session = {
|
|
210
|
+
user: e.user ?? this.session.user,
|
|
211
|
+
token: e.token ?? this.session.token,
|
|
212
|
+
validatedAt: n()
|
|
213
|
+
}, this.offline = !1, t && this.store.write(this.session), this.scheduleRenewal(), this.setState(this.session.user || this.hasLiveToken() ? "authenticated" : "guest", r);
|
|
214
|
+
}
|
|
215
|
+
endSession(e, t = "signed-out") {
|
|
216
|
+
clearTimeout(this.renewalTimer), this.renewalTimer = void 0, this.session = { validatedAt: n() }, this.store.write(this.session), this.setState("guest", t), e && this.emit({
|
|
217
|
+
type: "expired",
|
|
218
|
+
reason: e
|
|
76
219
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
220
|
+
}
|
|
221
|
+
expiresAt() {
|
|
222
|
+
return r(this.session.token?.expiresAt) ?? i(this.session.token?.accessToken);
|
|
223
|
+
}
|
|
224
|
+
hasLiveToken() {
|
|
225
|
+
if (!this.session.token?.accessToken) return !1;
|
|
226
|
+
let e = this.expiresAt();
|
|
227
|
+
return e === void 0 || e - s > n();
|
|
228
|
+
}
|
|
229
|
+
isFresh() {
|
|
230
|
+
return n() - this.session.validatedAt < this.revalidateSeconds;
|
|
231
|
+
}
|
|
232
|
+
hasEvidence() {
|
|
233
|
+
return this.session.token?.accessToken || this.session.token?.refreshToken ? !0 : !!e(this.hintCookie);
|
|
234
|
+
}
|
|
235
|
+
scheduleRenewal() {
|
|
236
|
+
clearTimeout(this.renewalTimer), this.renewalTimer = void 0;
|
|
237
|
+
let e = this.expiresAt();
|
|
238
|
+
if (e === void 0 || typeof window > "u" || this.state === "guest") return;
|
|
239
|
+
let t = Math.min((e - s - n()) * 1e3, l);
|
|
240
|
+
if (t <= 0) {
|
|
241
|
+
this.onRenewalDue();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this.renewalTimer = setTimeout(() => this.onRenewalDue(), t);
|
|
245
|
+
}
|
|
246
|
+
onRenewalDue() {
|
|
247
|
+
if (this.capabilities.renew) {
|
|
248
|
+
this.renew();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
this.endSession("expired");
|
|
252
|
+
}
|
|
253
|
+
attach() {
|
|
254
|
+
if (typeof window > "u" || this.detachListeners) return;
|
|
255
|
+
let e = () => {
|
|
256
|
+
document.visibilityState === "visible" && this.revalidate();
|
|
257
|
+
}, t = () => {
|
|
258
|
+
this.offline && this.revalidate(!0);
|
|
259
|
+
}, n = this.store.subscribe((e) => this.adoptFromStorage(e)), r = a(({ reason: e, url: t }) => {
|
|
260
|
+
let n = this.endpoints.filter(Boolean);
|
|
261
|
+
(n.length === 0 || n.some((e) => o(e, t))) && this.invalidate(e);
|
|
262
|
+
});
|
|
263
|
+
document.addEventListener("visibilitychange", e), window.addEventListener("online", t), this.detachListeners = () => {
|
|
264
|
+
document.removeEventListener("visibilitychange", e), window.removeEventListener("online", t), n(), r();
|
|
86
265
|
};
|
|
87
266
|
}
|
|
267
|
+
adoptFromStorage(e) {
|
|
268
|
+
if (!e?.token && !e?.user) {
|
|
269
|
+
clearTimeout(this.renewalTimer), this.renewalTimer = void 0, this.session = { validatedAt: n() }, this.setState("guest", "another-tab"), this.emit({ type: "logout" });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this.adopt({
|
|
273
|
+
ok: !0,
|
|
274
|
+
user: e.user,
|
|
275
|
+
token: e.token
|
|
276
|
+
}, !1);
|
|
277
|
+
}
|
|
88
278
|
};
|
|
89
279
|
//#endregion
|
|
90
|
-
export {
|
|
280
|
+
export { u as default };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Schema, TokenResult } from '@plitzi/sdk-shared';
|
|
2
|
+
export type StorageKind = Exclude<Schema['settings']['tokenStorage'], undefined>;
|
|
3
|
+
export type StoredSession<U> = {
|
|
4
|
+
version: number;
|
|
5
|
+
token?: TokenResult;
|
|
6
|
+
user?: U;
|
|
7
|
+
/**
|
|
8
|
+
* Unix seconds of the last time the backend answered about this session — including the answer that nobody is
|
|
9
|
+
* signed in. Storing the negative answer is what stops a signed-out visitor from paying for a request on every
|
|
10
|
+
* single page load just to be told the same thing again.
|
|
11
|
+
*/
|
|
12
|
+
validatedAt: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Where a session survives a reload. It holds the user next to the token on purpose: a token alone says a session is
|
|
16
|
+
* live but not whose it is, so a page that stored only the token had to ask the backend who it was talking to before
|
|
17
|
+
* it could render anything — which is the reason boot used to cost a round trip.
|
|
18
|
+
*/
|
|
19
|
+
declare class SessionStore<U> {
|
|
20
|
+
private readonly kind;
|
|
21
|
+
private readonly key;
|
|
22
|
+
constructor(kind: StorageKind, key: string);
|
|
23
|
+
private get area();
|
|
24
|
+
read(): StoredSession<U> | undefined;
|
|
25
|
+
write(session: Omit<StoredSession<U>, 'version'>): void;
|
|
26
|
+
clear(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Fires when another tab writes the session. Signing out in one tab has to end it in all of them, and a renewal in
|
|
29
|
+
* one has to be adopted by the rest — two tabs that each renew on their own would rotate the refresh token out from
|
|
30
|
+
* under each other, and whichever lost the race would sign its user out for no reason.
|
|
31
|
+
*/
|
|
32
|
+
subscribe(listener: (session?: StoredSession<U>) => void): () => void;
|
|
33
|
+
}
|
|
34
|
+
export default SessionStore;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/helpers/SessionStore.ts
|
|
2
|
+
var e = (e) => typeof e == "object" && !!e && e.version === 1, t = class {
|
|
3
|
+
kind;
|
|
4
|
+
key;
|
|
5
|
+
constructor(e, t) {
|
|
6
|
+
this.kind = e, this.key = t;
|
|
7
|
+
}
|
|
8
|
+
get area() {
|
|
9
|
+
try {
|
|
10
|
+
if (this.kind === "localStorage" && typeof localStorage < "u") return localStorage;
|
|
11
|
+
if (this.kind === "sessionStorage" && typeof sessionStorage < "u") return sessionStorage;
|
|
12
|
+
} catch {}
|
|
13
|
+
}
|
|
14
|
+
read() {
|
|
15
|
+
let t = this.area?.getItem(this.key);
|
|
16
|
+
if (t) try {
|
|
17
|
+
let n = JSON.parse(t);
|
|
18
|
+
return e(n) ? n : void 0;
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
write(e) {
|
|
24
|
+
try {
|
|
25
|
+
this.area?.setItem(this.key, JSON.stringify({
|
|
26
|
+
version: 1,
|
|
27
|
+
...e
|
|
28
|
+
}));
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
clear() {
|
|
32
|
+
try {
|
|
33
|
+
this.area?.removeItem(this.key);
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
subscribe(e) {
|
|
37
|
+
if (this.kind !== "localStorage" || typeof window > "u") return () => void 0;
|
|
38
|
+
let t = (t) => {
|
|
39
|
+
(t.key === null || t.key === this.key) && e(this.read());
|
|
40
|
+
};
|
|
41
|
+
return window.addEventListener("storage", t), () => window.removeEventListener("storage", t);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { t as default };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SessionHint = {
|
|
2
|
+
/** Unix seconds the session's access credential dies at. */
|
|
3
|
+
expiresAt: number;
|
|
4
|
+
/** Unix seconds renewal stops being possible at, when the backend states it. */
|
|
5
|
+
refreshExpiresAt?: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Reads the session hint your backend publishes beside its httpOnly session cookie (see `sessionHintCookie` in the
|
|
9
|
+
* space settings). It answers, with no request, the one question local storage cannot: whether this browser holds a
|
|
10
|
+
* session that some other page — a sibling app on the same domain, a social sign-in redirect — established.
|
|
11
|
+
*
|
|
12
|
+
* Its absence is an answer too, and the more valuable one: a configured hint that is not there means nobody is signed
|
|
13
|
+
* in, which is how a signed-out visitor gets a page rendered without a round trip to be told so.
|
|
14
|
+
*/
|
|
15
|
+
export declare const readSessionHint: (cookieName?: string) => SessionHint | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/helpers/sessionHint.ts
|
|
2
|
+
var e = (e) => {
|
|
3
|
+
let [t, n] = e.split("."), r = Number(t);
|
|
4
|
+
if (!Number.isFinite(r) || r <= 0) return;
|
|
5
|
+
let i = Number(n);
|
|
6
|
+
return {
|
|
7
|
+
expiresAt: r,
|
|
8
|
+
refreshExpiresAt: Number.isFinite(i) && i > 0 ? i : void 0
|
|
9
|
+
};
|
|
10
|
+
}, t = (t) => {
|
|
11
|
+
if (!(!t || typeof document > "u")) for (let n of document.cookie.split(";")) {
|
|
12
|
+
let r = n.indexOf("=");
|
|
13
|
+
if (r !== -1 && n.slice(0, r).trim() === t) return e(decodeURIComponent(n.slice(r + 1).trim()));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { t as readSessionHint };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `exp` claim of a JWT, in unix seconds, or undefined for a token that is opaque or unreadable.
|
|
3
|
+
*
|
|
4
|
+
* Decoded, never verified: a browser holds no signing secret, and a signature it cannot check tells it nothing. This
|
|
5
|
+
* is only ever used to skip asking a question the token already answers — "has this lapsed?" — and never to grant
|
|
6
|
+
* anything. A forged `exp` buys an attacker one refused request from the server, which is the only opinion that counts.
|
|
7
|
+
*/
|
|
8
|
+
export declare const tokenExpiresAt: (token?: string | null) => number | undefined;
|
|
9
|
+
/** Unix seconds, the unit every expiry in this package is stated in — `Date.now()` is the odd one out. */
|
|
10
|
+
export declare const nowInSeconds: () => number;
|
|
11
|
+
export declare const toSeconds: (expiry?: number | null) => number | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/helpers/tokenClaims.ts
|
|
2
|
+
var e = (e) => {
|
|
3
|
+
try {
|
|
4
|
+
let t = e.replaceAll("-", "+").replaceAll("_", "/"), n = t.padEnd(t.length + (4 - t.length % 4) % 4, "="), r = JSON.parse(atob(n));
|
|
5
|
+
return typeof r == "object" && r ? r : void 0;
|
|
6
|
+
} catch {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
}, t = (t) => {
|
|
10
|
+
if (!t) return;
|
|
11
|
+
let n = t.split(".");
|
|
12
|
+
if (n.length !== 3) return;
|
|
13
|
+
let r = e(n[1])?.exp;
|
|
14
|
+
return typeof r == "number" && Number.isFinite(r) ? r : void 0;
|
|
15
|
+
}, n = () => Math.floor(Date.now() / 1e3), r = 1e11, i = (e) => {
|
|
16
|
+
if (!(typeof e != "number" || !Number.isFinite(e) || e <= 0)) return Math.floor(e > r ? e / 1e3 : e);
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { n as nowInSeconds, i as toSeconds, t as tokenExpiresAt };
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { AuthManager } from '../../AuthManager';
|
|
2
|
-
import {
|
|
2
|
+
import { AuthProviderSettings } from '../../types';
|
|
3
|
+
import { AuthState, Server } from '@plitzi/sdk-shared';
|
|
3
4
|
export type UseAuthProps = {
|
|
4
5
|
server?: Server;
|
|
5
6
|
isHydrating?: boolean;
|
|
6
|
-
|
|
7
|
-
provider
|
|
8
|
-
|
|
9
|
-
userUrl?: Schema['settings']['userUrl'];
|
|
10
|
-
refreshUrl?: Schema['settings']['refreshUrl'];
|
|
11
|
-
logoutUrl?: Schema['settings']['logoutUrl'];
|
|
12
|
-
detailsPath?: Schema['settings']['detailsPath'];
|
|
13
|
-
tokenPath?: Schema['settings']['tokenPath'];
|
|
14
|
-
expirationTimePath?: Schema['settings']['expirationTimePath'];
|
|
7
|
+
provider?: string;
|
|
8
|
+
/** Held stable by the caller: a new object here builds a new provider and re-runs the whole boot decision. */
|
|
9
|
+
settings: AuthProviderSettings;
|
|
15
10
|
};
|
|
16
|
-
|
|
11
|
+
/**
|
|
12
|
+
* `loading` is what a space's pages are held back by, so it is deliberately narrow: it means "this page cannot be
|
|
13
|
+
* rendered yet", not "auth is doing something". A session restored from storage renders immediately and confirms
|
|
14
|
+
* itself behind the render, which is the difference between a page that appears at once and one that waits on a
|
|
15
|
+
* round trip to be told what it already knew.
|
|
16
|
+
*/
|
|
17
|
+
declare const useAuth: ({ server, isHydrating, provider, settings }: UseAuthProps) => {
|
|
17
18
|
manager: AuthManager<{
|
|
18
19
|
id: number;
|
|
19
20
|
username: string;
|
|
@@ -21,8 +22,35 @@ declare const useAuth: ({ server, isHydrating, tokenStorage, provider, loginUrl,
|
|
|
21
22
|
verified: boolean;
|
|
22
23
|
permissions: string[];
|
|
23
24
|
roles: string[];
|
|
24
|
-
}
|
|
25
|
+
}>;
|
|
26
|
+
state: AuthState;
|
|
27
|
+
/** Who the server rendered this page for, so a consumer can stand on it until the browser has decided. */
|
|
28
|
+
bootstrapUser: import('@plitzi/sdk-shared').User | undefined;
|
|
29
|
+
bootstrapToken: string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Who this browser can be seen to hold a session for, read synchronously before `init()` runs.
|
|
32
|
+
*
|
|
33
|
+
* Published for the same reason `bootstrapUser` is: the provider fills in from an effect, so on a
|
|
34
|
+
* client-rendered page the auth data source was EMPTY for the first commit — `{{user.*}}` rendered blank and
|
|
35
|
+
* filled in a moment later, on every reload of every signed-in visitor.
|
|
36
|
+
*/
|
|
37
|
+
peekedUser: {
|
|
38
|
+
id: number;
|
|
39
|
+
username: string;
|
|
40
|
+
email: string;
|
|
41
|
+
verified: boolean;
|
|
42
|
+
permissions: string[];
|
|
43
|
+
roles: string[];
|
|
44
|
+
} | undefined;
|
|
45
|
+
peekedToken: string | undefined;
|
|
25
46
|
loading: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Before the browser has decided, the server's answer stands. `state` only becomes `authenticated` from
|
|
49
|
+
* `init()`, which runs in an effect — and effects do not run during a server render, so this read was false on
|
|
50
|
+
* the server for a visitor the server had just resolved. Page-level access rules are evaluated against it, so
|
|
51
|
+
* an `accessLevel: 'authenticated'` page never won on the server, and the browser then picked a different page
|
|
52
|
+
* than the HTML it was hydrating.
|
|
53
|
+
*/
|
|
26
54
|
authenticated: boolean;
|
|
27
55
|
};
|
|
28
56
|
export default useAuth;
|
|
@@ -1,58 +1,45 @@
|
|
|
1
1
|
import { AuthManager as e } from "../../AuthManager.mjs";
|
|
2
2
|
import { useCallback as t, useEffect as n, useMemo as r, useState as i } from "react";
|
|
3
3
|
//#region src/hooks/useAuth/useAuth.ts
|
|
4
|
-
var a = ({ server: a, isHydrating: o = !1,
|
|
5
|
-
let
|
|
6
|
-
e.type === "state" && (
|
|
7
|
-
}, []),
|
|
8
|
-
let t = {};
|
|
9
|
-
c === "basic" && (t = {
|
|
10
|
-
tokenStorage: s,
|
|
11
|
-
loginUrl: l,
|
|
12
|
-
userUrl: u,
|
|
13
|
-
refreshUrl: d,
|
|
14
|
-
logoutUrl: f,
|
|
15
|
-
detailsPath: p,
|
|
16
|
-
tokenPath: m,
|
|
17
|
-
expirationTimePath: h,
|
|
18
|
-
isSSR: g
|
|
19
|
-
});
|
|
20
|
-
let n = new e(c, x, t);
|
|
21
|
-
return n.init(a?.authenticated ? a.user?.details : void 0, a?.skipAuth), n;
|
|
22
|
-
}, [
|
|
23
|
-
c,
|
|
24
|
-
x,
|
|
25
|
-
a?.authenticated,
|
|
26
|
-
a?.user?.details,
|
|
27
|
-
a?.skipAuth,
|
|
4
|
+
var a = ({ server: a, isHydrating: o = !1, provider: s = "", settings: c }) => {
|
|
5
|
+
let l = a?.authenticated ? a.user?.details : void 0, u = typeof a?.user?.accessToken == "string" ? a.user.accessToken : void 0, [d, f] = i("init"), p = t((e) => {
|
|
6
|
+
e.type === "state" && f(e.state);
|
|
7
|
+
}, []), m = r(() => new e(s, p, c), [
|
|
28
8
|
s,
|
|
29
|
-
l,
|
|
30
|
-
u,
|
|
31
|
-
d,
|
|
32
|
-
f,
|
|
33
9
|
p,
|
|
10
|
+
c
|
|
11
|
+
]), h = r(() => o || l ? void 0 : m.peek(), [
|
|
34
12
|
m,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
]);
|
|
38
|
-
return n(() => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
13
|
+
o,
|
|
14
|
+
l
|
|
15
|
+
]), g = d !== "init" && d !== "initLoading" || !!l || o || h?.state === "authenticated" || h?.state === "guest";
|
|
16
|
+
return n(() => (m.init({
|
|
17
|
+
user: l,
|
|
18
|
+
accessToken: u,
|
|
19
|
+
expiresAt: a?.user?.expiresAt,
|
|
20
|
+
skipAuth: a?.skipAuth
|
|
21
|
+
}), () => m.dispose()), [
|
|
22
|
+
m,
|
|
23
|
+
l,
|
|
24
|
+
u,
|
|
25
|
+
a?.user?.expiresAt,
|
|
26
|
+
a?.skipAuth
|
|
27
|
+
]), r(() => ({
|
|
28
|
+
manager: m,
|
|
29
|
+
state: d,
|
|
30
|
+
bootstrapUser: l,
|
|
31
|
+
bootstrapToken: u,
|
|
32
|
+
peekedUser: h?.user,
|
|
33
|
+
peekedToken: h?.token?.accessToken,
|
|
34
|
+
loading: d === "initLoading" || !g,
|
|
35
|
+
authenticated: d === "authenticated" || d === "init" && (!!l || h?.state === "authenticated")
|
|
52
36
|
}), [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
m,
|
|
38
|
+
d,
|
|
39
|
+
l,
|
|
40
|
+
u,
|
|
41
|
+
g,
|
|
42
|
+
h
|
|
56
43
|
]);
|
|
57
44
|
};
|
|
58
45
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { default as AuthContext } from './AuthContext';
|
|
2
2
|
import { default as AuthContextProvider } from './AuthContextProvider';
|
|
3
|
+
import { default as AuthProvider } from './AuthProvider';
|
|
3
4
|
import { default as useAuth } from './hooks/useAuth';
|
|
4
5
|
export * from './hooks/useAuth';
|
|
5
6
|
export * from './AuthContext';
|
|
6
7
|
export * from './AuthProvider';
|
|
7
8
|
export * from './AuthManager';
|
|
8
|
-
export
|
|
9
|
+
export * from './types';
|
|
10
|
+
export { readSessionHint } from './helpers/sessionHint';
|
|
11
|
+
export { nowInSeconds, tokenExpiresAt } from './helpers/tokenClaims';
|
|
12
|
+
export { useAuth, AuthContext, AuthContextProvider, AuthProvider };
|