fix: dumb auth
This commit is contained in:
@@ -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');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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: true },
|
||||||
data: { username: 'evan', is_admin: false },
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user