Refactor useAuth hook, settings components, and docs for formatting and readability improvements
- Consolidated multi-line arguments into single lines where appropriate in `useAuth`. - Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`). - Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`). - Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
This commit is contained in:
@@ -9,13 +9,13 @@
|
||||
* Design: Context handles dependency injection, Zustand handles state management
|
||||
*/
|
||||
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { createContext, useContext } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports -- This is the DI boundary, needs real store for production
|
||||
import { useAuthStore as useAuthStoreImpl } from "@/lib/stores/authStore";
|
||||
import type { User } from "@/lib/stores/authStore";
|
||||
import { useAuthStore as useAuthStoreImpl } from '@/lib/stores/authStore';
|
||||
import type { User } from '@/lib/stores/authStore';
|
||||
|
||||
/**
|
||||
* Authentication state shape
|
||||
@@ -31,7 +31,12 @@ interface AuthState {
|
||||
tokenExpiresAt: number | null;
|
||||
|
||||
// Actions
|
||||
setAuth: (user: User, accessToken: string, refreshToken: string, expiresIn?: number) => Promise<void>;
|
||||
setAuth: (
|
||||
user: User,
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
expiresIn?: number
|
||||
) => Promise<void>;
|
||||
setTokens: (accessToken: string, refreshToken: string, expiresIn?: number) => Promise<void>;
|
||||
setUser: (user: User) => void;
|
||||
clearAuth: () => Promise<void>;
|
||||
@@ -120,7 +125,7 @@ export function useAuth<T>(selector?: (state: AuthState) => T): AuthState | T {
|
||||
const storeHook = useContext(AuthContext);
|
||||
|
||||
if (!storeHook) {
|
||||
throw new Error("useAuth must be used within AuthProvider");
|
||||
throw new Error('useAuth must be used within AuthProvider');
|
||||
}
|
||||
|
||||
// Call the Zustand hook internally (follows React Rules of Hooks)
|
||||
|
||||
@@ -34,13 +34,10 @@ async function getEncryptionKey(): Promise<CryptoKey> {
|
||||
if (storedKey) {
|
||||
try {
|
||||
const keyData = JSON.parse(storedKey);
|
||||
return await crypto.subtle.importKey(
|
||||
'jwk',
|
||||
keyData,
|
||||
{ name: 'AES-GCM', length: 256 },
|
||||
true,
|
||||
['encrypt', 'decrypt']
|
||||
);
|
||||
return await crypto.subtle.importKey('jwk', keyData, { name: 'AES-GCM', length: 256 }, true, [
|
||||
'encrypt',
|
||||
'decrypt',
|
||||
]);
|
||||
} catch (error) {
|
||||
// Corrupted key, regenerate
|
||||
console.warn('Failed to import stored key, generating new key:', error);
|
||||
@@ -49,11 +46,10 @@ async function getEncryptionKey(): Promise<CryptoKey> {
|
||||
}
|
||||
|
||||
// Generate new key
|
||||
const key = await crypto.subtle.generateKey(
|
||||
{ name: 'AES-GCM', length: 256 },
|
||||
true,
|
||||
['encrypt', 'decrypt']
|
||||
);
|
||||
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
|
||||
'encrypt',
|
||||
'decrypt',
|
||||
]);
|
||||
|
||||
// Store key in sessionStorage
|
||||
try {
|
||||
@@ -86,11 +82,7 @@ export async function encryptData(data: string): Promise<string> {
|
||||
const encoder = new TextEncoder();
|
||||
const encodedData = encoder.encode(data);
|
||||
|
||||
const encryptedData = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv },
|
||||
key,
|
||||
encodedData
|
||||
);
|
||||
const encryptedData = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encodedData);
|
||||
|
||||
// Combine IV and encrypted data
|
||||
const combined = new Uint8Array(iv.length + encryptedData.byteLength);
|
||||
@@ -122,17 +114,13 @@ export async function decryptData(encryptedData: string): Promise<string> {
|
||||
const key = await getEncryptionKey();
|
||||
|
||||
// Decode from base64
|
||||
const combined = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
||||
const combined = Uint8Array.from(atob(encryptedData), (c) => c.charCodeAt(0));
|
||||
|
||||
// Extract IV and encrypted data
|
||||
const iv = combined.slice(0, 12);
|
||||
const data = combined.slice(12);
|
||||
|
||||
const decryptedData = await crypto.subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv },
|
||||
key,
|
||||
data
|
||||
);
|
||||
const decryptedData = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(decryptedData);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
// Authentication utilities
|
||||
// Examples: Token management, auth helpers, session utilities, etc.
|
||||
|
||||
export {
|
||||
encryptData,
|
||||
decryptData,
|
||||
clearEncryptionKey,
|
||||
} from './crypto';
|
||||
export { encryptData, decryptData, clearEncryptionKey } from './crypto';
|
||||
|
||||
export {
|
||||
saveTokens,
|
||||
|
||||
@@ -25,7 +25,10 @@ export type StorageMethod = 'cookie' | 'localStorage';
|
||||
* This flag is set by E2E tests to skip encryption for easier testing
|
||||
*/
|
||||
function isE2ETestMode(): boolean {
|
||||
return typeof window !== 'undefined' && (window as { __PLAYWRIGHT_TEST__?: boolean }).__PLAYWRIGHT_TEST__ === true;
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
(window as { __PLAYWRIGHT_TEST__?: boolean }).__PLAYWRIGHT_TEST__ === true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,10 +165,14 @@ export async function getTokens(): Promise<TokenStorage | null> {
|
||||
const parsed = JSON.parse(stored);
|
||||
|
||||
// Validate structure - must have required fields
|
||||
if (!parsed || typeof parsed !== 'object' ||
|
||||
!('accessToken' in parsed) || !('refreshToken' in parsed) ||
|
||||
(parsed.accessToken !== null && typeof parsed.accessToken !== 'string') ||
|
||||
(parsed.refreshToken !== null && typeof parsed.refreshToken !== 'string')) {
|
||||
if (
|
||||
!parsed ||
|
||||
typeof parsed !== 'object' ||
|
||||
!('accessToken' in parsed) ||
|
||||
!('refreshToken' in parsed) ||
|
||||
(parsed.accessToken !== null && typeof parsed.accessToken !== 'string') ||
|
||||
(parsed.refreshToken !== null && typeof parsed.refreshToken !== 'string')
|
||||
) {
|
||||
throw new Error('Invalid token structure');
|
||||
}
|
||||
|
||||
@@ -177,10 +184,14 @@ export async function getTokens(): Promise<TokenStorage | null> {
|
||||
const parsed = JSON.parse(decrypted);
|
||||
|
||||
// Validate structure - must have required fields
|
||||
if (!parsed || typeof parsed !== 'object' ||
|
||||
!('accessToken' in parsed) || !('refreshToken' in parsed) ||
|
||||
(parsed.accessToken !== null && typeof parsed.accessToken !== 'string') ||
|
||||
(parsed.refreshToken !== null && typeof parsed.refreshToken !== 'string')) {
|
||||
if (
|
||||
!parsed ||
|
||||
typeof parsed !== 'object' ||
|
||||
!('accessToken' in parsed) ||
|
||||
!('refreshToken' in parsed) ||
|
||||
(parsed.accessToken !== null && typeof parsed.accessToken !== 'string') ||
|
||||
(parsed.refreshToken !== null && typeof parsed.refreshToken !== 'string')
|
||||
) {
|
||||
/* istanbul ignore next - Validation error path */
|
||||
throw new Error('Invalid token structure');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user