fix: dumb auth

This commit is contained in:
2026-07-03 15:09:09 -04:00
parent 2c2eca8360
commit 45e018b024
3 changed files with 41 additions and 87 deletions
+4 -4
View File
@@ -13,7 +13,7 @@ import {
getAuthenticatedAuthState, getAuthenticatedAuthState,
getUnauthenticatedAuthState, getUnauthenticatedAuthState,
resolveAuthStateFromMe, resolveAuthStateFromMe,
validateAuthMutationResponse, authUserFromMutation,
} from './authHelpers'; } from './authHelpers';
interface AuthContextType extends AuthState { interface AuthContextType extends AuthState {
@@ -63,7 +63,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, },
}); });
const user = validateAuthMutationResponse(response, 200); const user = authUserFromMutation(response);
if (!user) { if (!user) {
setAuthState(getUnauthenticatedAuthState()); setAuthState(getUnauthenticatedAuthState());
throw new Error('Login failed'); throw new Error('Login failed');
@@ -91,7 +91,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, },
}); });
const user = validateAuthMutationResponse(response, 201); const user = authUserFromMutation(response);
if (!user) { if (!user) {
setAuthState(getUnauthenticatedAuthState()); setAuthState(getUnauthenticatedAuthState());
throw new Error('Registration failed'); throw new Error('Registration failed');
@@ -113,7 +113,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
logoutMutation.mutate(undefined, { logoutMutation.mutate(undefined, {
onSuccess: async () => { onSuccess: async () => {
setAuthState(getUnauthenticatedAuthState()); setAuthState(getUnauthenticatedAuthState());
await queryClient.removeQueries({ queryKey: getGetMeQueryKey() }); queryClient.removeQueries({ queryKey: getGetMeQueryKey() });
navigate('/login'); navigate('/login');
}, },
}); });
+21 -39
View File
@@ -2,9 +2,8 @@ import { describe, expect, it } from 'vitest';
import { import {
getCheckingAuthState, getCheckingAuthState,
getUnauthenticatedAuthState, getUnauthenticatedAuthState,
normalizeAuthenticatedUser,
resolveAuthStateFromMe, resolveAuthStateFromMe,
validateAuthMutationResponse, authUserFromMutation,
type AuthState, type AuthState,
} from './authHelpers'; } from './authHelpers';
@@ -15,20 +14,6 @@ const previousState: AuthState = {
}; };
describe('authHelpers', () => { describe('authHelpers', () => {
it('normalizes a valid authenticated user payload', () => {
expect(normalizeAuthenticatedUser({ username: 'evan', is_admin: true })).toEqual({
username: 'evan',
is_admin: true,
});
});
it('rejects invalid authenticated user payloads', () => {
expect(normalizeAuthenticatedUser(null)).toBeNull();
expect(normalizeAuthenticatedUser({ username: 'evan' })).toBeNull();
expect(normalizeAuthenticatedUser({ username: 123, is_admin: true })).toBeNull();
expect(normalizeAuthenticatedUser({ username: 'evan', is_admin: 'yes' })).toBeNull();
});
it('returns a checking state while preserving previous auth information', () => { it('returns a checking state while preserving previous auth information', () => {
expect( expect(
getCheckingAuthState({ getCheckingAuthState({
@@ -49,6 +34,7 @@ describe('authHelpers', () => {
meData: { meData: {
status: 200, status: 200,
data: { username: 'evan', is_admin: false }, data: { username: 'evan', is_admin: false },
headers: new Headers(),
}, },
meError: undefined, meError: undefined,
meLoading: false, meLoading: false,
@@ -66,6 +52,8 @@ describe('authHelpers', () => {
resolveAuthStateFromMe({ resolveAuthStateFromMe({
meData: { meData: {
status: 401, status: 401,
data: { code: 401, message: 'unauthorized' },
headers: new Headers(),
}, },
meError: undefined, meError: undefined,
meLoading: false, meLoading: false,
@@ -105,9 +93,7 @@ describe('authHelpers', () => {
it('returns the previous state with checking disabled when there is no decisive me result', () => { it('returns the previous state with checking disabled when there is no decisive me result', () => {
expect( expect(
resolveAuthStateFromMe({ resolveAuthStateFromMe({
meData: { meData: undefined,
status: 204,
},
meError: undefined, meError: undefined,
meLoading: false, meLoading: false,
previousState: { previousState: {
@@ -123,35 +109,31 @@ describe('authHelpers', () => {
}); });
}); });
it('validates auth mutation responses by expected status and payload shape', () => { it('extracts the user from successful login and register responses', () => {
expect( expect(
validateAuthMutationResponse( authUserFromMutation({
{
status: 200, status: 200,
data: { username: 'evan', is_admin: false }, data: { username: 'evan', is_admin: false },
}, headers: new Headers(),
200 })
)
).toEqual({ username: 'evan', is_admin: false }); ).toEqual({ username: 'evan', is_admin: false });
expect( expect(
validateAuthMutationResponse( authUserFromMutation({
{
status: 201, status: 201,
data: { username: 'evan', is_admin: false }, data: { username: 'evan', is_admin: true },
}, headers: new Headers(),
200 })
) ).toEqual({ username: 'evan', is_admin: true });
).toBeNull(); });
it('returns null for unsuccessful auth mutation responses', () => {
expect( expect(
validateAuthMutationResponse( authUserFromMutation({
{ status: 401,
status: 200, data: { code: 401, message: 'unauthorized' },
data: { username: 'evan' }, headers: new Headers(),
}, })
200
)
).toBeNull(); ).toBeNull();
}); });
}); });
+13 -41
View File
@@ -1,7 +1,11 @@
export interface AuthUser { import type {
username: string; getMeResponse,
is_admin: boolean; loginResponse,
} registerResponse,
} from '../generated/anthoLumeAPIV1';
import type { LoginResponse } from '../generated/model';
export type AuthUser = LoginResponse;
export interface AuthState { export interface AuthState {
isAuthenticated: boolean; isAuthenticated: boolean;
@@ -9,11 +13,6 @@ export interface AuthState {
isCheckingAuth: boolean; isCheckingAuth: boolean;
} }
interface ResponseLike {
status?: number;
data?: unknown;
}
export function getUnauthenticatedAuthState(): AuthState { export function getUnauthenticatedAuthState(): AuthState {
return { return {
isAuthenticated: false, isAuthenticated: false,
@@ -38,27 +37,8 @@ export function getAuthenticatedAuthState(user: AuthUser): AuthState {
}; };
} }
export function normalizeAuthenticatedUser(value: unknown): AuthUser | null {
if (!value || typeof value !== 'object') {
return null;
}
if (!('username' in value) || typeof value.username !== 'string') {
return null;
}
if (!('is_admin' in value) || typeof value.is_admin !== 'boolean') {
return null;
}
return {
username: value.username,
is_admin: value.is_admin,
};
}
export function resolveAuthStateFromMe(params: { export function resolveAuthStateFromMe(params: {
meData?: ResponseLike; meData?: getMeResponse;
meError?: unknown; meError?: unknown;
meLoading: boolean; meLoading: boolean;
previousState: AuthState; previousState: AuthState;
@@ -70,10 +50,7 @@ export function resolveAuthStateFromMe(params: {
} }
if (meData?.status === 200) { if (meData?.status === 200) {
const user = normalizeAuthenticatedUser(meData.data); return getAuthenticatedAuthState(meData.data);
if (user) {
return getAuthenticatedAuthState(user);
}
} }
if (meError || meData?.status === 401) { if (meError || meData?.status === 401) {
@@ -86,13 +63,8 @@ export function resolveAuthStateFromMe(params: {
}; };
} }
export function validateAuthMutationResponse( export function authUserFromMutation(
response: ResponseLike, response: loginResponse | registerResponse
expectedStatus: number
): AuthUser | null { ): AuthUser | null {
if (response.status !== expectedStatus) { return response.status === 200 || response.status === 201 ? response.data : null;
return null;
}
return normalizeAuthenticatedUser(response.data);
} }