@stacksjs/browser 0.74.59 → 0.74.61
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readSessionHandoff,stripSessionHandoff,useStorage}from"@stacksjs/composables";import{ref}from"@stacksjs/stx";import{withCsrfHeader}from"./csrf";const token=useStorage("token",""),refreshToken=useStorage("refresh_token",""),stacksConfig=globalThis.__STACKS_CONFIG__||{},baseUrl=stacksConfig.API_URL||(typeof window<"u"?window.location.origin:""),ME_PATH=stacksConfig.AUTH_ME_PATH||"/api/me",REFRESH_PATH=stacksConfig.AUTH_REFRESH_PATH||"/auth/refresh",user=ref(null),isAuthenticated=ref(!1);let inFlightRefresh=null;function applySessionHandoff(pack){const resolved=pack??(typeof window<"u"?readSessionHandoff(window.location.hash):null);if(!resolved)return!1;token.value=resolved.token;if(resolved.refreshToken)refreshToken.value=resolved.refreshToken;if(resolved.user!==void 0&&resolved.user!==null)user.value=resolved.user;isAuthenticated.value=!0;if(typeof window<"u"&&!pack){const rest=stripSessionHandoff(window.location.hash);window.history.replaceState(null,"",`${window.location.pathname}${window.location.search}${rest}`)}return!0}function clearSession(){token.value="";refreshToken.value="";user.value=null;isAuthenticated.value=!1}async function performRefresh(){if(!refreshToken.value)return!1;const response=await fetch(`${baseUrl}${REFRESH_PATH}`,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json",Accept:"application/json"}),body:JSON.stringify({refresh_token:refreshToken.value})});if(!response.ok){clearSession();return!1}const data=await response.json(),nextAccess=data.access_token??data.token;if(!nextAccess){clearSession();return!1}token.value=nextAccess;if(data.refresh_token)refreshToken.value=data.refresh_token;return!0}export async function refreshSession(){if(inFlightRefresh)return inFlightRefresh;inFlightRefresh=performRefresh().catch(()=>{return!1}).finally(()=>{inFlightRefresh=null});return inFlightRefresh}export async function authFetch(input,init={}){const send=()=>
|
|
1
|
+
import{readSessionHandoff,stripSessionHandoff,useStorage}from"@stacksjs/composables";import{ref}from"@stacksjs/stx";import{CSRF_COOKIE_NAME,readCsrfToken,withCsrfHeader}from"./csrf";const token=useStorage("token",""),refreshToken=useStorage("refresh_token",""),stacksConfig=globalThis.__STACKS_CONFIG__||{},baseUrl=stacksConfig.API_URL||(typeof window<"u"?window.location.origin:""),ME_PATH=stacksConfig.AUTH_ME_PATH||"/api/me",REFRESH_PATH=stacksConfig.AUTH_REFRESH_PATH||"/auth/refresh",user=ref(null),isAuthenticated=ref(!1);let inFlightRefresh=null;function applySessionHandoff(pack){const resolved=pack??(typeof window<"u"?readSessionHandoff(window.location.hash):null);if(!resolved)return!1;token.value=resolved.token;if(resolved.refreshToken)refreshToken.value=resolved.refreshToken;if(resolved.user!==void 0&&resolved.user!==null)user.value=resolved.user;isAuthenticated.value=!0;if(typeof window<"u"&&!pack){const rest=stripSessionHandoff(window.location.hash);window.history.replaceState(null,"",`${window.location.pathname}${window.location.search}${rest}`)}return!0}function clearSession(){token.value="";refreshToken.value="";user.value=null;isAuthenticated.value=!1}async function performRefresh(){if(!refreshToken.value)return!1;const response=await fetch(`${baseUrl}${REFRESH_PATH}`,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json",Accept:"application/json"}),body:JSON.stringify({refresh_token:refreshToken.value})});if(!response.ok){clearSession();return!1}const data=await response.json(),nextAccess=data.access_token??data.token;if(!nextAccess){clearSession();return!1}token.value=nextAccess;if(data.refresh_token)refreshToken.value=data.refresh_token;return!0}export async function refreshSession(){if(inFlightRefresh)return inFlightRefresh;inFlightRefresh=performRefresh().catch(()=>{return!1}).finally(()=>{inFlightRefresh=null});return inFlightRefresh}export async function authFetch(input,init={}){const send=()=>{const target=input.startsWith("http")?input:`${baseUrl}${input}`,headers=new Headers(init.headers);if(token.value)headers.set("Authorization",`Bearer ${token.value}`);headers.set("Accept","application/json");const method=(init.method||"GET").toUpperCase(),pageOrigin=typeof window<"u"?window.location?.origin:void 0,sameOrigin=!!pageOrigin&&new URL(target,pageOrigin).origin===pageOrigin;if(!["GET","HEAD","OPTIONS"].includes(method)&&sameOrigin&&!headers.has(CSRF_COOKIE_NAME)){const csrfToken=readCsrfToken();if(csrfToken)headers.set(CSRF_COOKIE_NAME,csrfToken)}return fetch(target,{credentials:"same-origin",...init,headers})},response=await send();if(response.status!==401||!refreshToken.value)return response;if(!await refreshSession())return response;return await send()}export function useAuth(){function isTwoFactorChallenge(data){if(!data||typeof data!=="object")return!1;const candidate=data;return candidate.requires_two_factor===!0&&typeof candidate.challenge_token==="string"&&candidate.challenge_token.length>0}function isLoginResponse(data){if(!data||typeof data!=="object")return!1;const candidate=data;return typeof candidate.token==="string"&&candidate.token.length>0&&typeof candidate.user==="object"&&candidate.user!==null&&(candidate.refresh_token===void 0||typeof candidate.refresh_token==="string"&&candidate.refresh_token.length>0)}function storeTokenPack(data){token.value=data.token;refreshToken.value=data.refresh_token??""}async function fetchAuthUser(){try{const response=await authFetch(ME_PATH);if(!response.ok){clearSession();return null}const data=await response.json();user.value=data;isAuthenticated.value=!0;return data}catch(error){console.error("Error fetching user:",error);isAuthenticated.value=!1;user.value=null;return null}}async function checkAuthentication(){try{return await fetchAuthUser()!==null}catch(error){console.error("Error checking authentication:",error);return!1}}async function register(user){const url=`${baseUrl}/register`,data=await(await fetch(url,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json"}),body:JSON.stringify(user)})).json();if(isRegisterError(data))return data;if(isRegisterResponse(data)){token.value=data.token;const refreshed=data.refresh_token;refreshToken.value=refreshed??"";return data}return data}function isRegisterError(data){return"errors"in data}function isRegisterResponse(data){return"token"in data&&"user"in data}async function login(user){try{const url=`${baseUrl}/login`,response=await fetch(url,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json"}),body:JSON.stringify(user)});if(!response.ok){const errorData=await response.json();throw Error(errorData.message||`HTTP error! status: ${response.status}`)}const data=await response.json();if(isTwoFactorChallenge(data))return data;if(!isLoginResponse(data))throw Error("The login response did not contain a valid session.");storeTokenPack(data);await fetchAuthUser();return data}catch(error){return error}}async function verifyTwoFactorLogin(challengeToken,code){try{const response=await fetch(`${baseUrl}/verify-two-factor-login`,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({"Content-Type":"application/json"}),body:JSON.stringify({challenge_token:challengeToken,code})});if(!response.ok){const errorData=await response.json();throw Error(errorData.message||`HTTP error! status: ${response.status}`)}const data=await response.json();if(!isLoginResponse(data))throw Error("The two-factor response did not contain a valid session.");storeTokenPack(data);await fetchAuthUser();return data}catch(error){return error}}async function logout(){const currentToken=token.value,response=await fetch(`${baseUrl}/logout`,{method:"POST",credentials:"same-origin",headers:withCsrfHeader({...currentToken?{Authorization:`Bearer ${currentToken}`}:{},Accept:"application/json"})});if(!response.ok)throw Error(`Logout failed with status ${response.status}`);clearSession()}async function completeSocialLogin(pack){const applied=applySessionHandoff(pack),confirmed=await fetchAuthUser();if(confirmed||applied)return confirmed;return null}return{user,isAuthenticated,token,getToken:()=>token.value,register,login,verifyTwoFactorLogin,logout,fetchAuthUser,completeSocialLogin,checkAuthentication,refreshToken,getRefreshToken:()=>refreshToken.value,refreshSession,authFetch}}export function authGuard(options={}){const guest=options.guest??!1,{isAuthenticated}=useAuth();if(guest){if(isAuthenticated.value)window.location.replace("/");return}if(!isAuthenticated.value)window.location.replace("/login")}
|
|
@@ -7,7 +7,9 @@ export declare interface ValidationError {
|
|
|
7
7
|
}[]
|
|
8
8
|
}
|
|
9
9
|
export declare interface RegisterError {
|
|
10
|
-
errors
|
|
10
|
+
errors?: ResponseError
|
|
11
|
+
error?: string
|
|
12
|
+
message?: string
|
|
11
13
|
}
|
|
12
14
|
// LoginAction/RegisterAction/AuthUserAction respond via `response.json(...)`,
|
|
13
15
|
// which serializes flat (see @stacksjs/bun-router's ResponseFactory.json) —
|
|
@@ -26,7 +28,7 @@ export declare interface RegisterResponse {
|
|
|
26
28
|
// is kept alongside `access_token` for backward compatibility.
|
|
27
29
|
export declare interface LoginResponse {
|
|
28
30
|
access_token: string
|
|
29
|
-
refresh_token
|
|
31
|
+
refresh_token?: string
|
|
30
32
|
token_type: string
|
|
31
33
|
expires_in: number
|
|
32
34
|
token: string
|
|
@@ -36,6 +38,10 @@ export declare interface LoginResponse {
|
|
|
36
38
|
name: string
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
export declare interface TwoFactorLoginChallenge {
|
|
42
|
+
requires_two_factor: true
|
|
43
|
+
challenge_token: string
|
|
44
|
+
}
|
|
39
45
|
export declare interface Response<T> {
|
|
40
46
|
errors: ResponseError
|
|
41
47
|
data: T
|
|
@@ -43,6 +49,7 @@ export declare interface Response<T> {
|
|
|
43
49
|
export declare interface AuthUser {
|
|
44
50
|
email: string
|
|
45
51
|
password: string
|
|
52
|
+
remember?: boolean
|
|
46
53
|
}
|
|
47
54
|
export declare interface ErrorResponse {
|
|
48
55
|
message: string
|
|
@@ -55,12 +62,13 @@ export declare interface UserData {
|
|
|
55
62
|
export declare interface AuthComposable {
|
|
56
63
|
isAuthenticated: Ref<boolean>
|
|
57
64
|
user: Ref<UserData | null>
|
|
58
|
-
login: (user: AuthUser) => Promise<
|
|
65
|
+
login: (user: AuthUser) => Promise<LoginResult | LoginError>
|
|
66
|
+
verifyTwoFactorLogin: (challengeToken: string, code: string) => Promise<LoginResponse | LoginError>
|
|
59
67
|
register: (user: AuthUser) => Promise<RegisterResponse | RegisterError>
|
|
60
68
|
fetchAuthUser: () => Promise<UserData | null>
|
|
61
69
|
completeSocialLogin: (pack?: SessionHandoffPack | null) => Promise<UserData | null>
|
|
62
70
|
checkAuthentication: () => Promise<boolean>
|
|
63
|
-
logout: () => void
|
|
71
|
+
logout: () => Promise<void>
|
|
64
72
|
getToken: () => string | null
|
|
65
73
|
token: Ref<string | null>
|
|
66
74
|
refreshToken: Ref<string | null>
|
|
@@ -72,4 +80,5 @@ export type ResponseError = {
|
|
|
72
80
|
error: string
|
|
73
81
|
} | ValidationError;
|
|
74
82
|
export type LoginError = RegisterError;
|
|
83
|
+
export type LoginResult = LoginResponse | TwoFactorLoginChallenge;
|
|
75
84
|
export type MeResponse = UserData;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/browser",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.61",
|
|
6
6
|
"description": "Stacks core frontend/browser functionalities.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"prepublishOnly": "bun run build"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@stacksjs/composables": "0.74.
|
|
79
|
-
"@stacksjs/datetime": "0.74.
|
|
80
|
-
"@stacksjs/strings": "0.74.
|
|
78
|
+
"@stacksjs/composables": "0.74.61",
|
|
79
|
+
"@stacksjs/datetime": "0.74.61",
|
|
80
|
+
"@stacksjs/strings": "0.74.61",
|
|
81
81
|
"@stacksjs/stx": "^0.2.286",
|
|
82
82
|
"bun-query-builder": "^0.2.70"
|
|
83
83
|
},
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@stacksjs/utils": "0.74.
|
|
93
|
+
"@stacksjs/utils": "0.74.61",
|
|
94
94
|
"@stripe/stripe-js": "^9.10.0",
|
|
95
95
|
"better-dx": "^0.2.24"
|
|
96
96
|
}
|