feat: admin frontend - React + Vite, auth pages, user management, roles, permissions, webhooks, devices, logs
This commit is contained in:
7
frontend/admin/src/lib/storage/index.ts
Normal file
7
frontend/admin/src/lib/storage/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
getRefreshToken,
|
||||
setRefreshToken,
|
||||
clearRefreshToken,
|
||||
hasRefreshToken,
|
||||
hasSessionPresenceCookie,
|
||||
} from './token-storage'
|
||||
68
frontend/admin/src/lib/storage/token-storage.test.ts
Normal file
68
frontend/admin/src/lib/storage/token-storage.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
clearRefreshToken,
|
||||
getRefreshToken,
|
||||
hasRefreshToken,
|
||||
hasSessionPresenceCookie,
|
||||
setRefreshToken,
|
||||
} from './token-storage'
|
||||
|
||||
const originalDocument = globalThis.document
|
||||
|
||||
describe('token-storage', () => {
|
||||
afterEach(() => {
|
||||
clearRefreshToken()
|
||||
vi.restoreAllMocks()
|
||||
|
||||
Object.defineProperty(globalThis, 'document', {
|
||||
configurable: true,
|
||||
value: originalDocument,
|
||||
})
|
||||
})
|
||||
|
||||
it('stores refresh tokens in memory and normalizes empty values to null', () => {
|
||||
setRefreshToken(' refresh-token ')
|
||||
|
||||
expect(getRefreshToken()).toBe('refresh-token')
|
||||
expect(hasRefreshToken()).toBe(true)
|
||||
|
||||
setRefreshToken(' ')
|
||||
expect(getRefreshToken()).toBeNull()
|
||||
expect(hasRefreshToken()).toBe(false)
|
||||
|
||||
setRefreshToken(undefined)
|
||||
expect(getRefreshToken()).toBeNull()
|
||||
})
|
||||
|
||||
it('clears the in-memory refresh token explicitly', () => {
|
||||
setRefreshToken('token-to-clear')
|
||||
expect(hasRefreshToken()).toBe(true)
|
||||
|
||||
clearRefreshToken()
|
||||
|
||||
expect(getRefreshToken()).toBeNull()
|
||||
expect(hasRefreshToken()).toBe(false)
|
||||
})
|
||||
|
||||
it('detects the session presence cookie when it is present among other cookies', () => {
|
||||
vi.spyOn(document, 'cookie', 'get').mockReturnValue('foo=bar; ums_session_present=1; theme=dark')
|
||||
|
||||
expect(hasSessionPresenceCookie()).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when the session presence cookie is absent', () => {
|
||||
vi.spyOn(document, 'cookie', 'get').mockReturnValue('foo=bar; theme=dark')
|
||||
|
||||
expect(hasSessionPresenceCookie()).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false when document is unavailable', () => {
|
||||
Object.defineProperty(globalThis, 'document', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
})
|
||||
|
||||
expect(hasSessionPresenceCookie()).toBe(false)
|
||||
})
|
||||
})
|
||||
38
frontend/admin/src/lib/storage/token-storage.ts
Normal file
38
frontend/admin/src/lib/storage/token-storage.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* In-memory refresh token storage.
|
||||
*
|
||||
* The authoritative session continuity mechanism is now the backend-managed
|
||||
* HttpOnly refresh cookie. This module only keeps a process-local copy so the
|
||||
* current tab can still send an explicit logout payload when available.
|
||||
*/
|
||||
|
||||
let refreshToken: string | null = null
|
||||
const SESSION_PRESENCE_COOKIE_NAME = 'ums_session_present'
|
||||
|
||||
export function getRefreshToken(): string | null {
|
||||
return refreshToken
|
||||
}
|
||||
|
||||
export function setRefreshToken(token: string | null | undefined): void {
|
||||
const value = (token || '').trim()
|
||||
refreshToken = value || null
|
||||
}
|
||||
|
||||
export function clearRefreshToken(): void {
|
||||
refreshToken = null
|
||||
}
|
||||
|
||||
export function hasRefreshToken(): boolean {
|
||||
return refreshToken !== null
|
||||
}
|
||||
|
||||
export function hasSessionPresenceCookie(): boolean {
|
||||
if (typeof document === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
return document.cookie
|
||||
.split(';')
|
||||
.map((cookie) => cookie.trim())
|
||||
.some((cookie) => cookie.startsWith(`${SESSION_PRESENCE_COOKIE_NAME}=`))
|
||||
}
|
||||
Reference in New Issue
Block a user