Files
inbuxa-admin/src/stores/authStore.ts
T
jcoffey-dev 3a33f3d514 Mark the files this fork changed (AGPL section 5(a))
Publishing the source is what asks for it: a modified version has to carry
prominent notices saying it was modified, with a date. These files already
added a Coffey Labs copyright line beside upstream's, which implies as much
without saying it; now they say it.

38 files, found by diffing against the merge base with upstream rather than
by guessing. Upstream's own notices are untouched. The build is unchanged.
2026-09-19 23:48:44 -07:00

157 lines
4.4 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useCacheStore } from '@/stores/cacheStore';
interface AccountInfo {
name: string;
isPersonal: boolean;
}
interface AuthState {
accessToken: string | null;
refreshToken: string | null;
tokenExpiresAt: number | null;
tokenEndpoint: string | null;
endSessionEndpoint: string | null;
accounts: Record<string, AccountInfo>;
primaryAccountId: string | null;
activeAccountId: string | null;
apiUrl: string | null;
maxObjectsInGet: number;
maxObjectsInSet: number;
/** INBUXA: who is signed in, from the JMAP session (`username`). */
username: string | null;
setTokens: (
access: string,
refresh: string,
expiresIn: number,
tokenEndpoint: string,
endSessionEndpoint?: string | null,
) => void;
setSession: (
accounts: Record<string, AccountInfo>,
primaryAccountId: string,
apiUrl: string,
maxObjectsInGet?: number,
maxObjectsInSet?: number,
) => void;
switchAccount: (accountId: string) => void;
setUsername: (username: string | null) => void;
logout: () => void;
isAuthenticated: () => boolean;
isTokenExpiringSoon: () => boolean;
}
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
accessToken: null,
refreshToken: null,
tokenExpiresAt: null,
tokenEndpoint: null,
endSessionEndpoint: null,
accounts: {},
primaryAccountId: null,
activeAccountId: null,
username: null,
setUsername: (username) => {
set({ username });
},
apiUrl: null,
maxObjectsInGet: 500,
maxObjectsInSet: 500,
setTokens: (access, refresh, expiresIn, tokenEndpoint, endSessionEndpoint) => {
set({
accessToken: access,
refreshToken: refresh,
tokenExpiresAt: Date.now() + expiresIn * 1000,
tokenEndpoint,
endSessionEndpoint: endSessionEndpoint ?? null,
});
},
setSession: (accounts, primaryAccountId, apiUrl, maxObjectsInGet, maxObjectsInSet) => {
const current = get().activeAccountId;
set({
accounts,
primaryAccountId,
activeAccountId: current && accounts[current] ? current : primaryAccountId,
apiUrl,
...(maxObjectsInGet !== undefined ? { maxObjectsInGet } : {}),
...(maxObjectsInSet !== undefined ? { maxObjectsInSet } : {}),
});
},
switchAccount: (accountId) => {
const { accounts, activeAccountId } = get();
if (accounts[accountId] && accountId !== activeAccountId) {
set({ activeAccountId: accountId });
useCacheStore.getState().clearAll();
}
},
logout: () => {
set({
accessToken: null,
refreshToken: null,
tokenExpiresAt: null,
tokenEndpoint: null,
endSessionEndpoint: null,
accounts: {},
primaryAccountId: null,
activeAccountId: null,
apiUrl: null,
username: null,
});
},
isAuthenticated: () => {
const { accessToken, tokenExpiresAt } = get();
return accessToken !== null && tokenExpiresAt !== null && Date.now() < tokenExpiresAt;
},
isTokenExpiringSoon: () => {
const { tokenExpiresAt } = get();
if (tokenExpiresAt === null) return false;
return tokenExpiresAt - Date.now() < 60_000;
},
}),
{
name: 'inbuxa-auth',
storage: {
getItem: (name) => {
const value = sessionStorage.getItem(name);
return value ? JSON.parse(value) : null;
},
setItem: (name, value) => {
sessionStorage.setItem(name, JSON.stringify(value));
},
removeItem: (name) => {
sessionStorage.removeItem(name);
},
},
partialize: (state) =>
({
accessToken: state.accessToken,
refreshToken: state.refreshToken,
tokenExpiresAt: state.tokenExpiresAt,
tokenEndpoint: state.tokenEndpoint,
endSessionEndpoint: state.endSessionEndpoint,
activeAccountId: state.activeAccountId,
}) as AuthState,
},
),
);