@pinooxhq/auth 0.1.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.
@@ -0,0 +1,877 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
4
+ var vueRouter = require('vue-router');
5
+
6
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
+ // src/vue/useAuth.ts
8
+
9
+ // src/core/logger.ts
10
+ var defaultSink = (event, enabled) => {
11
+ if (!enabled && event.level === "debug") {
12
+ return;
13
+ }
14
+ const prefix = `[pinoox-auth:${event.code}]`;
15
+ const payload = event.context ? [prefix, event.message, event.context] : [prefix, event.message];
16
+ switch (event.level) {
17
+ case "error":
18
+ console.error(...payload);
19
+ break;
20
+ case "warn":
21
+ console.warn(...payload);
22
+ break;
23
+ case "info":
24
+ console.info(...payload);
25
+ break;
26
+ default:
27
+ console.debug(...payload);
28
+ }
29
+ };
30
+ function createLogger(debug = false, sink) {
31
+ let customSink = null;
32
+ const emit = (event) => {
33
+ if (customSink) {
34
+ customSink(event);
35
+ return;
36
+ }
37
+ defaultSink(event, debug);
38
+ };
39
+ return {
40
+ emit,
41
+ debug: (code, message, context) => emit({ level: "debug", code, message, context }),
42
+ info: (code, message, context) => emit({ level: "info", code, message, context }),
43
+ warn: (code, message, context) => emit({ level: "warn", code, message, context }),
44
+ error: (code, message, context) => emit({ level: "error", code, message, context }),
45
+ setSink: (next) => {
46
+ customSink = next;
47
+ }
48
+ };
49
+ }
50
+
51
+ // src/core/returnPath.ts
52
+ var DEFAULT_FALLBACK = "/";
53
+ var DEFAULT_BLOCKED = ["/account"];
54
+ function isSafeReturnPath(value, blockedPrefixes = DEFAULT_BLOCKED) {
55
+ if (typeof value !== "string") {
56
+ return false;
57
+ }
58
+ const trimmed = value.trim();
59
+ if (!trimmed.startsWith("/") || trimmed.startsWith("//")) {
60
+ return false;
61
+ }
62
+ if (trimmed.includes("://") || trimmed.includes("\\")) {
63
+ return false;
64
+ }
65
+ for (const prefix of blockedPrefixes) {
66
+ if (!prefix) {
67
+ continue;
68
+ }
69
+ if (trimmed === prefix || trimmed.startsWith(`${prefix}/`)) {
70
+ return false;
71
+ }
72
+ }
73
+ return true;
74
+ }
75
+ function resolveReturnPath(queryOrPath = void 0, fallback = DEFAULT_FALLBACK, blockedPrefixes = DEFAULT_BLOCKED) {
76
+ let candidate = queryOrPath;
77
+ if (queryOrPath && typeof queryOrPath === "object" && !Array.isArray(queryOrPath)) {
78
+ candidate = queryOrPath.redirect;
79
+ }
80
+ if (candidate === void 0 && typeof window !== "undefined") {
81
+ candidate = new URLSearchParams(window.location.search).get("redirect");
82
+ }
83
+ if (isSafeReturnPath(candidate, blockedPrefixes)) {
84
+ return candidate.trim();
85
+ }
86
+ return fallback;
87
+ }
88
+ function resolveSiteOrigin(bootUrl, explicit) {
89
+ const candidates = [explicit, bootUrl?.SITE];
90
+ for (const value of candidates) {
91
+ if (typeof value !== "string" || !value) {
92
+ continue;
93
+ }
94
+ try {
95
+ return new URL(value).origin;
96
+ } catch {
97
+ const cleaned = value.replace(/\/$/, "");
98
+ if (/^https?:\/\//i.test(cleaned)) {
99
+ return cleaned;
100
+ }
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+ function toAbsoluteReturnUrl(path, siteOrigin) {
106
+ if (typeof path !== "string" || !path.startsWith("/")) {
107
+ return path;
108
+ }
109
+ if (!siteOrigin || typeof window === "undefined") {
110
+ return path;
111
+ }
112
+ if (window.location.origin === siteOrigin) {
113
+ return path;
114
+ }
115
+ return `${siteOrigin.replace(/\/$/, "")}${path}`;
116
+ }
117
+ function redirectToReturn(queryOrPath, options = {}) {
118
+ if (typeof window === "undefined") {
119
+ return;
120
+ }
121
+ const path = resolveReturnPath(queryOrPath, options.fallback, options.blockedPrefixes);
122
+ window.location.href = toAbsoluteReturnUrl(path, options.siteOrigin);
123
+ }
124
+
125
+ // src/core/resolveConfig.ts
126
+ function readBoot() {
127
+ if (typeof globalThis === "undefined") {
128
+ return {};
129
+ }
130
+ const g = globalThis;
131
+ if (g.__PINOOX__ && typeof g.__PINOOX__ === "object") {
132
+ return g.__PINOOX__;
133
+ }
134
+ if (g.PINOOX?.URL) {
135
+ return { url: g.PINOOX.URL };
136
+ }
137
+ return {};
138
+ }
139
+ function normalizeMode(value) {
140
+ const mode = String(value ?? "jwt").toLowerCase();
141
+ if (mode === "cookie" || mode === "session" || mode === "jwt") {
142
+ return mode;
143
+ }
144
+ return "jwt";
145
+ }
146
+ function normalizeBaseUrl(value) {
147
+ if (typeof value !== "string" || !value.trim()) {
148
+ return null;
149
+ }
150
+ let base = value.trim().replace(/\/$/, "");
151
+ if (!/^https?:\/\//i.test(base) && !base.startsWith("/")) {
152
+ base = `/${base}`;
153
+ }
154
+ return base;
155
+ }
156
+ function joinUrl(base, path) {
157
+ if (!path) {
158
+ return base;
159
+ }
160
+ if (/^https?:\/\//i.test(path)) {
161
+ return path;
162
+ }
163
+ if (path.startsWith("/")) {
164
+ return path;
165
+ }
166
+ const normalizedBase = base.replace(/\/$/, "");
167
+ return `${normalizedBase}/${path.replace(/^\//, "")}`;
168
+ }
169
+ function resolveEndpoint(path, fallback, baseUrl) {
170
+ const value = typeof path === "string" && path.trim() !== "" ? path.trim() : fallback;
171
+ if (/^https?:\/\//i.test(value) || value.startsWith("/")) {
172
+ return value;
173
+ }
174
+ if (baseUrl) {
175
+ return joinUrl(baseUrl, value);
176
+ }
177
+ return value;
178
+ }
179
+ function inferStrategy(bootAuth, explicit) {
180
+ if (explicit) {
181
+ return normalizeStrategy(explicit) ?? explicit;
182
+ }
183
+ const fromBoot = normalizeStrategy(bootAuth?.strategy);
184
+ if (fromBoot) {
185
+ return fromBoot;
186
+ }
187
+ if (bootAuth?.source) {
188
+ return "remote";
189
+ }
190
+ return "local";
191
+ }
192
+ function normalizeStrategy(value) {
193
+ if (value === "local" || value === "provider") {
194
+ return "local";
195
+ }
196
+ if (value === "remote" || value === "consumer" || value === "external") {
197
+ return "remote";
198
+ }
199
+ return null;
200
+ }
201
+ function defaultEndpoints(apiBase, strategy, baseUrl) {
202
+ if (strategy === "remote") {
203
+ if (baseUrl) {
204
+ return {
205
+ login: "auth/login",
206
+ logout: "auth/logout",
207
+ me: "auth/get"
208
+ };
209
+ }
210
+ return {
211
+ login: "/account/api/v1/auth/login",
212
+ logout: "/account/api/v1/auth/logout",
213
+ me: "/account/api/v1/auth/get"
214
+ };
215
+ }
216
+ return {
217
+ login: joinUrl(apiBase, "auth/login"),
218
+ logout: joinUrl(apiBase, "auth/logout"),
219
+ me: joinUrl(apiBase, "auth/get")
220
+ };
221
+ }
222
+ function resolveConfig(options = {}, logger) {
223
+ const boot = options.boot ?? readBoot();
224
+ const bootAuth = boot.auth ?? {};
225
+ const apiBase = (options.apiBase ?? boot.url?.API ?? "").replace(/\/$/, "");
226
+ const strategy = inferStrategy(bootAuth, options.strategy);
227
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? bootAuth.baseUrl ?? null);
228
+ const defaults = defaultEndpoints(apiBase || "/", strategy, baseUrl);
229
+ const bootEndpoints = bootAuth.endpoints ?? {};
230
+ const endpoints = {
231
+ login: resolveEndpoint(options.endpoints?.login ?? bootEndpoints.login, defaults.login, baseUrl),
232
+ logout: resolveEndpoint(options.endpoints?.logout ?? bootEndpoints.logout, defaults.logout, baseUrl),
233
+ me: resolveEndpoint(options.endpoints?.me ?? bootEndpoints.me, defaults.me, baseUrl)
234
+ };
235
+ const key = bootAuth.key || options.key || "";
236
+ const mode = normalizeMode(bootAuth.mode || options.mode);
237
+ const debug = options.debug ?? (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && !!undefined?.DEV);
238
+ if (!boot.auth && !options.key && !options.mode) {
239
+ logger?.warn("config.missing", "No __PINOOX__.auth and no explicit mode/key overrides", {
240
+ hasBoot: Object.keys(boot).length > 0
241
+ });
242
+ }
243
+ if (!key) {
244
+ logger?.warn("config.missing", "auth.key is empty \u2014 token storage will fail until key is provided", {
245
+ bootAuth
246
+ });
247
+ }
248
+ const loginUrl = options.loginUrl ?? bootAuth.loginUrl ?? (strategy === "remote" ? "/account/login" : null);
249
+ const appPath = typeof boot.url?.BASE === "string" && boot.url.BASE || typeof boot.url?.APP === "string" && boot.url.APP || (apiBase.match(/^(\/[^/]+)/)?.[1] ?? null);
250
+ const siteOrigin = resolveSiteOrigin(boot.url, options.siteOrigin);
251
+ const resolved = {
252
+ mode,
253
+ key,
254
+ provider: bootAuth.provider ?? null,
255
+ source: bootAuth.source ?? null,
256
+ strategy,
257
+ loginUrl,
258
+ baseUrl,
259
+ endpoints,
260
+ apiBase,
261
+ siteOrigin,
262
+ appPath,
263
+ debug: !!debug
264
+ };
265
+ logger?.debug("config.resolved", "Auth config resolved", {
266
+ mode: resolved.mode,
267
+ key: resolved.key,
268
+ provider: resolved.provider,
269
+ source: resolved.source,
270
+ strategy: resolved.strategy,
271
+ baseUrl: resolved.baseUrl,
272
+ endpoints: resolved.endpoints,
273
+ siteOrigin: resolved.siteOrigin,
274
+ appPath: resolved.appPath
275
+ });
276
+ return resolved;
277
+ }
278
+
279
+ // src/core/storage.ts
280
+ function readCookie(name) {
281
+ if (typeof document === "undefined") {
282
+ return null;
283
+ }
284
+ const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
285
+ return match ? decodeURIComponent(match[1]) : null;
286
+ }
287
+ function writeCookie(name, value) {
288
+ if (typeof document === "undefined") {
289
+ return;
290
+ }
291
+ const maxAge = 60 * 60 * 24 * 90;
292
+ document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; SameSite=Lax`;
293
+ }
294
+ function deleteCookie(name) {
295
+ if (typeof document === "undefined") {
296
+ return;
297
+ }
298
+ document.cookie = `${name}=; path=/; max-age=0; SameSite=Lax`;
299
+ }
300
+ function isDev() {
301
+ try {
302
+ return typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && !!undefined?.DEV;
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+ function createStorage(key, logger) {
308
+ const syncDevCookie = isDev();
309
+ return {
310
+ get: () => {
311
+ if (!key) {
312
+ return null;
313
+ }
314
+ try {
315
+ if (typeof localStorage !== "undefined") {
316
+ const fromStorage = localStorage.getItem(key)?.trim() || null;
317
+ if (fromStorage) {
318
+ if (syncDevCookie) {
319
+ writeCookie(key, fromStorage);
320
+ }
321
+ return fromStorage;
322
+ }
323
+ }
324
+ } catch (error) {
325
+ logger?.warn("token.storage", "Failed to read localStorage", { error: String(error) });
326
+ }
327
+ if (!syncDevCookie) {
328
+ return null;
329
+ }
330
+ const fromCookie = readCookie(key);
331
+ if (fromCookie) {
332
+ try {
333
+ localStorage.setItem(key, fromCookie);
334
+ } catch {
335
+ }
336
+ return fromCookie;
337
+ }
338
+ return null;
339
+ },
340
+ set: (token) => {
341
+ if (!key) {
342
+ logger?.warn("mismatch.key", "Cannot store token without auth.key");
343
+ return;
344
+ }
345
+ if (typeof localStorage === "undefined") {
346
+ return;
347
+ }
348
+ try {
349
+ if (token) {
350
+ const value = token.trim();
351
+ localStorage.setItem(key, value);
352
+ logger?.debug("token.stored", "Token stored", { key });
353
+ if (syncDevCookie) {
354
+ writeCookie(key, value);
355
+ }
356
+ } else {
357
+ localStorage.removeItem(key);
358
+ logger?.debug("token.cleared", "Token cleared", { key });
359
+ if (syncDevCookie) {
360
+ deleteCookie(key);
361
+ }
362
+ }
363
+ } catch (error) {
364
+ logger?.warn("token.storage", "Failed to write storage", { error: String(error) });
365
+ }
366
+ },
367
+ clear: () => {
368
+ if (!key || typeof localStorage === "undefined") {
369
+ return;
370
+ }
371
+ try {
372
+ localStorage.removeItem(key);
373
+ } catch {
374
+ }
375
+ if (syncDevCookie) {
376
+ deleteCookie(key);
377
+ }
378
+ logger?.debug("token.cleared", "Token cleared", { key });
379
+ }
380
+ };
381
+ }
382
+
383
+ // src/strategies/local.ts
384
+ function unwrapPayload(data) {
385
+ if (!data || typeof data !== "object") {
386
+ return {};
387
+ }
388
+ const record = data;
389
+ if (record.data && typeof record.data === "object") {
390
+ return record.data;
391
+ }
392
+ return record;
393
+ }
394
+ function extractTokenAndUser(payload) {
395
+ const data = unwrapPayload(payload);
396
+ const tokenCandidate = typeof data.token === "string" && data.token || typeof payload?.token === "string" && payload.token || null;
397
+ const userCandidate = (data.user && typeof data.user === "object" ? data.user : null) || (data.username || data.email || data.user_id ? data : null);
398
+ return {
399
+ token: tokenCandidate,
400
+ user: userCandidate
401
+ };
402
+ }
403
+ async function localLogin(config, http, storage, credentials, logger) {
404
+ const response = await http.request({
405
+ url: config.endpoints.login,
406
+ method: "POST",
407
+ body: credentials,
408
+ credentials: "include",
409
+ headers: {
410
+ "Content-Type": "application/json",
411
+ "X-Requested-With": "XMLHttpRequest"
412
+ }
413
+ });
414
+ if (!response.ok) {
415
+ logger?.error("session.unauthorized", "Login request failed", {
416
+ status: response.status,
417
+ url: config.endpoints.login
418
+ });
419
+ throw Object.assign(new Error("Login failed"), { status: response.status, data: response.data });
420
+ }
421
+ const { token, user } = extractTokenAndUser(response.data);
422
+ if (config.mode === "jwt" && !token) {
423
+ logger?.warn("mismatch.mode", "JWT mode expected token in login response but none found", {
424
+ mode: config.mode,
425
+ url: config.endpoints.login
426
+ });
427
+ }
428
+ if (token) {
429
+ storage.set(token);
430
+ }
431
+ logger?.info("session.ok", "Login succeeded", { hasToken: !!token, hasUser: !!user });
432
+ return { token, user, raw: response.data };
433
+ }
434
+
435
+ // src/strategies/remote.ts
436
+ function buildRemoteLoginUrl(config, returnPath, logger) {
437
+ const loginUrl = config.loginUrl ?? "/account/login";
438
+ const blocked = config.appPath ? [config.appPath, "/account"] : ["/account"];
439
+ const safeRedirect = resolveReturnPath(
440
+ returnPath ?? (typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}` : "/"),
441
+ "/",
442
+ blocked
443
+ );
444
+ const url = `${loginUrl}?redirect=${encodeURIComponent(safeRedirect)}`;
445
+ logger?.info("redirect.login", "Redirecting to remote login", { url, returnPath: safeRedirect });
446
+ return url;
447
+ }
448
+ function redirectToRemoteLogin(config, returnPath, logger) {
449
+ if (typeof window === "undefined") {
450
+ return;
451
+ }
452
+ window.location.href = buildRemoteLoginUrl(config, returnPath, logger);
453
+ }
454
+
455
+ // src/http/types.ts
456
+ function createFetchProvider() {
457
+ return {
458
+ async request(input) {
459
+ const headers = {
460
+ Accept: "application/json",
461
+ ...input.headers
462
+ };
463
+ let body;
464
+ if (input.body !== void 0 && input.body !== null) {
465
+ if (typeof input.body === "string" || input.body instanceof FormData || input.body instanceof Blob) {
466
+ body = input.body;
467
+ } else {
468
+ headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
469
+ body = JSON.stringify(input.body);
470
+ }
471
+ }
472
+ const response = await fetch(input.url, {
473
+ method: input.method ?? "GET",
474
+ headers,
475
+ body,
476
+ credentials: input.credentials ?? "include"
477
+ });
478
+ const contentType = response.headers.get("content-type") ?? "";
479
+ let data;
480
+ if (contentType.includes("application/json")) {
481
+ data = await response.json();
482
+ } else {
483
+ data = await response.text();
484
+ }
485
+ return {
486
+ status: response.status,
487
+ data,
488
+ headers: response.headers,
489
+ ok: response.ok
490
+ };
491
+ }
492
+ };
493
+ }
494
+
495
+ // src/core/createAuth.ts
496
+ var defaultInstance = null;
497
+ function getAuth() {
498
+ if (!defaultInstance) {
499
+ throw new Error("@pinooxhq/auth: call createAuth() before getAuth()");
500
+ }
501
+ return defaultInstance;
502
+ }
503
+ function createAuth(options = {}) {
504
+ const logger = options.logger ?? createLogger(options.debug);
505
+ const config = resolveConfig(options, logger);
506
+ const storage = createStorage(config.key, logger);
507
+ let http = options.http ?? createFetchProvider();
508
+ let token = storage.get();
509
+ let user = null;
510
+ let authenticated = false;
511
+ const listeners = /* @__PURE__ */ new Map();
512
+ const emit = (event, payload) => {
513
+ const set = listeners.get(event);
514
+ if (!set) {
515
+ return;
516
+ }
517
+ for (const handler of set) {
518
+ try {
519
+ handler(payload);
520
+ } catch (error) {
521
+ logger.error("error", "Event handler failed", { event, error: String(error) });
522
+ }
523
+ }
524
+ };
525
+ const syncToken = (value) => {
526
+ token = value;
527
+ storage.set(value);
528
+ };
529
+ const getAuthHeader = () => {
530
+ if (config.mode !== "jwt" || !token) {
531
+ return null;
532
+ }
533
+ return token.toLowerCase().startsWith("bearer ") ? token : `Bearer ${token}`;
534
+ };
535
+ const getRequestAuth = () => ({
536
+ header: getAuthHeader(),
537
+ credentials: "include",
538
+ mode: config.mode,
539
+ key: config.key
540
+ });
541
+ const authorize = (headers = {}) => {
542
+ const auth = getRequestAuth();
543
+ const next = { ...headers };
544
+ if (auth.header) {
545
+ next.Authorization = auth.header;
546
+ }
547
+ logger.debug("request.auth", "Authorized request headers", {
548
+ mode: auth.mode,
549
+ hasHeader: !!auth.header
550
+ });
551
+ return { ...auth, headers: next };
552
+ };
553
+ const withAuthHeaders = (headers = {}) => {
554
+ return authorize(headers).headers;
555
+ };
556
+ const loginFromResponse = (payload) => {
557
+ const extracted = extractTokenAndUser(payload);
558
+ if (config.mode === "jwt" && !extracted.token) {
559
+ logger.warn("mismatch.mode", "JWT mode expected token in response but none found", {
560
+ mode: config.mode
561
+ });
562
+ emit("mismatch", { code: "mismatch.mode", payload });
563
+ }
564
+ if (extracted.token) {
565
+ syncToken(extracted.token);
566
+ }
567
+ if (extracted.user) {
568
+ user = extracted.user;
569
+ }
570
+ authenticated = !!(extracted.token || config.mode !== "jwt");
571
+ emit("login", { token: extracted.token, user: extracted.user });
572
+ return { ...extracted, raw: payload };
573
+ };
574
+ const me = async () => {
575
+ const current = storage.get();
576
+ token = current;
577
+ if (config.mode === "jwt" && !current) {
578
+ authenticated = false;
579
+ user = null;
580
+ logger.debug("session.unauthorized", "No token for me()", { mode: config.mode });
581
+ return null;
582
+ }
583
+ try {
584
+ const response = await http.request({
585
+ url: config.endpoints.me,
586
+ method: "GET",
587
+ credentials: "include",
588
+ headers: withAuthHeaders({
589
+ Accept: "application/json",
590
+ "X-Requested-With": "XMLHttpRequest"
591
+ })
592
+ });
593
+ if (response.status === 401) {
594
+ syncToken(null);
595
+ user = null;
596
+ authenticated = false;
597
+ logger.warn("session.unauthorized", "Session validation returned 401", {
598
+ url: config.endpoints.me
599
+ });
600
+ emit("unauthorized", { status: 401 });
601
+ return null;
602
+ }
603
+ if (!response.ok) {
604
+ logger.error("error", "me() request failed", { status: response.status });
605
+ emit("error", { status: response.status, data: response.data });
606
+ return null;
607
+ }
608
+ const extracted = extractTokenAndUser(response.data);
609
+ user = extracted.user ?? response.data;
610
+ authenticated = true;
611
+ logger.info("session.ok", "Session validated", { hasUser: !!user });
612
+ return user;
613
+ } catch (error) {
614
+ logger.error("error", "me() threw", { error: String(error) });
615
+ emit("error", error);
616
+ return null;
617
+ }
618
+ };
619
+ const login = async (credentials) => {
620
+ if (config.strategy === "remote") {
621
+ redirectToRemoteLogin(config, void 0, logger);
622
+ return;
623
+ }
624
+ if (!credentials?.password) {
625
+ throw new Error("@pinooxhq/auth: login() requires credentials for local strategy");
626
+ }
627
+ const result = await localLogin(config, http, storage, credentials, logger);
628
+ token = result.token ?? storage.get();
629
+ user = result.user;
630
+ authenticated = !!(result.token || config.mode !== "jwt");
631
+ emit("login", result);
632
+ return result;
633
+ };
634
+ const logout = async () => {
635
+ try {
636
+ await http.request({
637
+ url: config.endpoints.logout,
638
+ method: "GET",
639
+ credentials: "include",
640
+ headers: withAuthHeaders({
641
+ Accept: "application/json",
642
+ "X-Requested-With": "XMLHttpRequest"
643
+ })
644
+ });
645
+ } catch (error) {
646
+ logger.warn("error", "Remote logout failed; clearing local session anyway", {
647
+ error: String(error)
648
+ });
649
+ }
650
+ syncToken(null);
651
+ user = null;
652
+ authenticated = false;
653
+ emit("logout");
654
+ logger.info("token.cleared", "Logged out");
655
+ };
656
+ const instance = {
657
+ config,
658
+ logger,
659
+ get user() {
660
+ return user;
661
+ },
662
+ set user(value) {
663
+ user = value;
664
+ },
665
+ get token() {
666
+ return token ?? storage.get();
667
+ },
668
+ set token(value) {
669
+ syncToken(value);
670
+ },
671
+ get isAuthenticated() {
672
+ return authenticated && (!!token || config.mode !== "jwt");
673
+ },
674
+ set isAuthenticated(value) {
675
+ authenticated = value;
676
+ },
677
+ login,
678
+ logout,
679
+ me,
680
+ getToken: () => token ?? storage.get(),
681
+ setToken: (value) => {
682
+ syncToken(value);
683
+ },
684
+ clearToken: () => {
685
+ syncToken(null);
686
+ authenticated = false;
687
+ },
688
+ getAuthHeader,
689
+ authorize,
690
+ getRequestAuth,
691
+ setHttp: (client) => {
692
+ http = client;
693
+ },
694
+ loginFromResponse,
695
+ redirectToLogin: (returnPath) => {
696
+ if (config.strategy === "remote") {
697
+ redirectToRemoteLogin(config, returnPath, logger);
698
+ return;
699
+ }
700
+ const url = config.loginUrl ?? config.endpoints.login;
701
+ logger.info("redirect.login", "Redirecting to login", { url });
702
+ if (typeof window !== "undefined") {
703
+ window.location.href = url;
704
+ }
705
+ },
706
+ getReturnPath: (queryOrPath, fallback = "/") => {
707
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
708
+ return resolveReturnPath(queryOrPath, fallback, blocked);
709
+ },
710
+ getReturnUrl: (queryOrPath, fallback = "/") => {
711
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
712
+ const path = resolveReturnPath(queryOrPath, fallback, blocked);
713
+ return toAbsoluteReturnUrl(path, config.siteOrigin);
714
+ },
715
+ redirectBack: (queryOrPath, fallback = "/") => {
716
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
717
+ logger.info("redirect.back", "Redirecting after auth", { queryOrPath, fallback });
718
+ redirectToReturn(queryOrPath, {
719
+ fallback,
720
+ siteOrigin: config.siteOrigin,
721
+ blockedPrefixes: blocked
722
+ });
723
+ },
724
+ getRedirectQuery: (queryOrPath, fallback = "/") => {
725
+ const blocked = config.appPath ? [config.appPath] : ["/account"];
726
+ return { redirect: resolveReturnPath(queryOrPath, fallback, blocked) };
727
+ },
728
+ notifyUnauthorized: (payload) => {
729
+ syncToken(null);
730
+ user = null;
731
+ authenticated = false;
732
+ logger.warn("session.unauthorized", "Unauthorized notified", {
733
+ payload: payload ? String(payload) : void 0
734
+ });
735
+ emit("unauthorized", payload ?? { status: 401 });
736
+ },
737
+ on: (event, handler) => {
738
+ if (!listeners.has(event)) {
739
+ listeners.set(event, /* @__PURE__ */ new Set());
740
+ }
741
+ listeners.get(event).add(handler);
742
+ return () => {
743
+ listeners.get(event)?.delete(handler);
744
+ };
745
+ },
746
+ off: (event, handler) => {
747
+ listeners.get(event)?.delete(handler);
748
+ }
749
+ };
750
+ defaultInstance = instance;
751
+ emit("config", config);
752
+ return instance;
753
+ }
754
+
755
+ // src/vue/useAuth.ts
756
+ var vueBound = null;
757
+ function resolveAuthInstance() {
758
+ try {
759
+ return getAuth();
760
+ } catch {
761
+ return createAuth();
762
+ }
763
+ }
764
+ function createAuthPlugin(options = {}) {
765
+ const auth = createAuth(options);
766
+ return {
767
+ install() {
768
+ },
769
+ auth
770
+ };
771
+ }
772
+ function useAuth() {
773
+ if (vueBound) {
774
+ return vueBound;
775
+ }
776
+ const auth = resolveAuthInstance();
777
+ const user = vue.ref(auth.user);
778
+ const token = vue.ref(auth.getToken());
779
+ const syncFromAuth = () => {
780
+ user.value = auth.user;
781
+ token.value = auth.getToken();
782
+ };
783
+ auth.on("login", syncFromAuth);
784
+ auth.on("logout", syncFromAuth);
785
+ const isAuthenticated = vue.computed(() => {
786
+ return auth.isAuthenticated || !!token.value && !!user.value;
787
+ });
788
+ const me = async () => {
789
+ const profile = await auth.me();
790
+ syncFromAuth();
791
+ return profile;
792
+ };
793
+ const canAccess = async (refresh = false) => {
794
+ token.value = auth.getToken();
795
+ if (!refresh && auth.isAuthenticated) {
796
+ return true;
797
+ }
798
+ if (!token.value && auth.config.mode === "jwt") {
799
+ auth.isAuthenticated = false;
800
+ user.value = null;
801
+ return false;
802
+ }
803
+ const profile = await me();
804
+ return !!profile || auth.isAuthenticated;
805
+ };
806
+ const login = async (credentials) => {
807
+ const result = await auth.login(credentials);
808
+ syncFromAuth();
809
+ return result;
810
+ };
811
+ const logout = async () => {
812
+ await auth.logout();
813
+ syncFromAuth();
814
+ };
815
+ vueBound = {
816
+ auth,
817
+ user,
818
+ token,
819
+ isAuthenticated,
820
+ login,
821
+ logout,
822
+ me,
823
+ canAccess
824
+ };
825
+ return vueBound;
826
+ }
827
+ function useAuthRedirect(fallback = "/") {
828
+ const route = vueRouter.useRoute();
829
+ const auth = resolveAuthInstance();
830
+ const returnPath = vue.computed(() => auth.getReturnPath(route.query, fallback));
831
+ const returnUrl = vue.computed(() => auth.getReturnUrl(route.query, fallback));
832
+ const redirectQuery = vue.computed(() => auth.getRedirectQuery(route.query, fallback));
833
+ return {
834
+ returnPath,
835
+ returnUrl,
836
+ redirectQuery,
837
+ resolveRedirect: () => returnUrl.value,
838
+ redirectBack: () => auth.redirectBack(route.query, fallback)
839
+ };
840
+ }
841
+ function createPiniaAuthStore(defineStore, id = "pinoox-auth") {
842
+ return defineStore(id, () => {
843
+ const { auth, user, token, isAuthenticated, login, logout, me, canAccess } = useAuth();
844
+ return {
845
+ auth: isAuthenticated,
846
+ user,
847
+ token,
848
+ isAuth: isAuthenticated,
849
+ getUser: user,
850
+ login: (loginKey, userData = null) => {
851
+ auth.setToken(loginKey);
852
+ token.value = loginKey;
853
+ auth.isAuthenticated = true;
854
+ if (userData) {
855
+ auth.user = userData;
856
+ user.value = userData;
857
+ }
858
+ },
859
+ logout,
860
+ canUserAccess: canAccess,
861
+ syncTokenFromStorage: () => {
862
+ const latest = auth.getToken();
863
+ token.value = latest;
864
+ return !!latest;
865
+ },
866
+ me,
867
+ loginWithCredentials: login
868
+ };
869
+ });
870
+ }
871
+
872
+ exports.createAuthPlugin = createAuthPlugin;
873
+ exports.createPiniaAuthStore = createPiniaAuthStore;
874
+ exports.useAuth = useAuth;
875
+ exports.useAuthRedirect = useAuthRedirect;
876
+ //# sourceMappingURL=index.cjs.map
877
+ //# sourceMappingURL=index.cjs.map