mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
feat: add Tier 3 shared auth store patterns
- Create @manacore/shared-auth-stores package with Svelte 5 factories: - createAuthStore<T>(): Generic factory with custom auth service adapter - createSupabaseAuthStore(): Direct Supabase integration for simpler setups - Add standardized auth error types to @manacore/shared-types: - AuthErrorCode enum for consistent error handling - mapSupabaseErrorToCode() helper function - createAuthError() factory function This enables apps to share auth state management while supporting both middleware-based auth (ManaCore/Manadeck) and direct Supabase auth (ManaCore-web simple mode). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
10325026f9
commit
9449fff6f7
8 changed files with 717 additions and 0 deletions
32
packages/shared-auth-stores/package.json
Normal file
32
packages/shared-auth-stores/package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "@manacore/shared-auth-stores",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"svelte": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"svelte": "./src/index.ts",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"type-check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-types": "workspace:*"
|
||||
}
|
||||
}
|
||||
186
packages/shared-auth-stores/src/createAuthStore.svelte.ts
Normal file
186
packages/shared-auth-stores/src/createAuthStore.svelte.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* Svelte 5 Auth Store Factory
|
||||
*
|
||||
* Creates a reactive auth store using Svelte 5 runes.
|
||||
* Generic over user type to support app-specific user models.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // In your app's auth store file
|
||||
* import { createAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { authService } from '$lib/auth';
|
||||
* import type { AppUser } from '$lib/types';
|
||||
*
|
||||
* export const authStore = createAuthStore<AppUser>(authService);
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { AuthServiceAdapter, AuthResult, BaseUser } from './types';
|
||||
|
||||
/**
|
||||
* Create a Svelte 5 runes-based auth store
|
||||
*
|
||||
* @param authService - Auth service adapter implementing the AuthServiceAdapter interface
|
||||
* @returns Reactive auth store with state and actions
|
||||
*/
|
||||
export function createAuthStore<TUser extends BaseUser>(authService: AuthServiceAdapter<TUser>) {
|
||||
// Reactive state using Svelte 5 runes
|
||||
let user = $state<TUser | null>(null);
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
return {
|
||||
// Reactive getters
|
||||
get user() {
|
||||
return user;
|
||||
},
|
||||
get loading() {
|
||||
return loading;
|
||||
},
|
||||
get error() {
|
||||
return error;
|
||||
},
|
||||
get isAuthenticated() {
|
||||
return !!user;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize auth state from stored tokens/session
|
||||
*/
|
||||
async initialize() {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const isAuth = await authService.isAuthenticated();
|
||||
if (isAuth) {
|
||||
user = await authService.getUserFromToken();
|
||||
} else {
|
||||
user = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize auth:', e);
|
||||
error = e instanceof Error ? e.message : 'Failed to initialize authentication';
|
||||
user = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set user manually (useful for SSR hydration)
|
||||
*/
|
||||
setUser(newUser: TUser | null) {
|
||||
user = newUser;
|
||||
error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
async signIn(email: string, password: string): Promise<AuthResult<TUser>> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const result = await authService.signIn(email, password);
|
||||
if (result.success) {
|
||||
user = await authService.getUserFromToken();
|
||||
} else {
|
||||
error = result.error || 'Sign in failed';
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Sign in failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign up with email and password
|
||||
*/
|
||||
async signUp(email: string, password: string): Promise<AuthResult<TUser>> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const result = await authService.signUp(email, password);
|
||||
if (result.success && !result.needsVerification) {
|
||||
user = await authService.getUserFromToken();
|
||||
} else if (!result.success) {
|
||||
error = result.error || 'Sign up failed';
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Sign up failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send password reset email
|
||||
*/
|
||||
async forgotPassword(email: string): Promise<{ success: boolean; error?: string }> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const result = await authService.forgotPassword(email);
|
||||
if (!result.success) {
|
||||
error = result.error || 'Password reset failed';
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Password reset failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign out user
|
||||
*/
|
||||
async signOut() {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
await authService.signOut();
|
||||
user = null;
|
||||
} catch (e) {
|
||||
console.error('Sign out failed:', e);
|
||||
error = e instanceof Error ? e.message : 'Sign out failed';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check authentication status
|
||||
*/
|
||||
async checkAuth(): Promise<boolean> {
|
||||
try {
|
||||
const isAuth = await authService.isAuthenticated();
|
||||
if (!isAuth) {
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Auth check failed:', e);
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear error state
|
||||
*/
|
||||
clearError() {
|
||||
error = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
/**
|
||||
* Supabase Auth Store Factory
|
||||
*
|
||||
* Creates a reactive auth store that works directly with Supabase client.
|
||||
* Useful for apps that use Supabase directly without middleware.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createSupabaseAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { createBrowserClient } from '@supabase/ssr';
|
||||
*
|
||||
* const supabase = createBrowserClient(url, key);
|
||||
* export const authStore = createSupabaseAuthStore(supabase);
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { BaseUser, AuthResult } from './types';
|
||||
|
||||
/**
|
||||
* Minimal Supabase client interface
|
||||
* Only requires the auth methods we use
|
||||
*/
|
||||
interface SupabaseClientLike {
|
||||
auth: {
|
||||
getSession(): Promise<{ data: { session: { user: { id: string; email?: string } } | null } }>;
|
||||
signInWithPassword(credentials: {
|
||||
email: string;
|
||||
password: string;
|
||||
}): Promise<{ error: { message: string } | null }>;
|
||||
signUp(credentials: {
|
||||
email: string;
|
||||
password: string;
|
||||
}): Promise<{ data: { session: unknown }; error: { message: string } | null }>;
|
||||
resetPasswordForEmail(
|
||||
email: string,
|
||||
options?: { redirectTo?: string }
|
||||
): Promise<{ error: { message: string } | null }>;
|
||||
signOut(): Promise<{ error: { message: string } | null }>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a Supabase auth store
|
||||
*/
|
||||
export interface CreateSupabaseAuthStoreOptions {
|
||||
/** URL to redirect to after password reset */
|
||||
passwordResetRedirectUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default user type for Supabase auth
|
||||
*/
|
||||
export interface SupabaseUser extends BaseUser {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Svelte 5 runes-based auth store for Supabase
|
||||
*
|
||||
* @param getSupabaseClient - Function that returns a Supabase client
|
||||
* @param options - Configuration options
|
||||
* @returns Reactive auth store with state and actions
|
||||
*/
|
||||
export function createSupabaseAuthStore(
|
||||
getSupabaseClient: () => SupabaseClientLike,
|
||||
options: CreateSupabaseAuthStoreOptions = {}
|
||||
) {
|
||||
// Reactive state using Svelte 5 runes
|
||||
let user = $state<SupabaseUser | null>(null);
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
/**
|
||||
* Get user from current session
|
||||
*/
|
||||
async function getUserFromSession(): Promise<SupabaseUser | null> {
|
||||
const supabase = getSupabaseClient();
|
||||
const {
|
||||
data: { session }
|
||||
} = await supabase.auth.getSession();
|
||||
if (!session?.user) return null;
|
||||
return {
|
||||
id: session.user.id,
|
||||
email: session.user.email || ''
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
// Reactive getters
|
||||
get user() {
|
||||
return user;
|
||||
},
|
||||
get loading() {
|
||||
return loading;
|
||||
},
|
||||
get error() {
|
||||
return error;
|
||||
},
|
||||
get isAuthenticated() {
|
||||
return !!user;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize auth state from Supabase session
|
||||
*/
|
||||
async initialize() {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
user = await getUserFromSession();
|
||||
} catch (e) {
|
||||
console.error('Failed to initialize auth:', e);
|
||||
error = e instanceof Error ? e.message : 'Failed to initialize authentication';
|
||||
user = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set user manually
|
||||
*/
|
||||
setUser(newUser: SupabaseUser | null) {
|
||||
user = newUser;
|
||||
error = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign in with email and password
|
||||
*/
|
||||
async signIn(email: string, password: string): Promise<AuthResult<SupabaseUser>> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const supabase = getSupabaseClient();
|
||||
const { error: authError } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
if (authError) {
|
||||
error = authError.message;
|
||||
return { success: false, error: authError.message };
|
||||
}
|
||||
|
||||
user = await getUserFromSession();
|
||||
return { success: true, user: user || undefined };
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Sign in failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign up with email and password
|
||||
*/
|
||||
async signUp(email: string, password: string): Promise<AuthResult<SupabaseUser>> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const supabase = getSupabaseClient();
|
||||
const { data, error: authError } = await supabase.auth.signUp({
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
if (authError) {
|
||||
error = authError.message;
|
||||
return { success: false, error: authError.message };
|
||||
}
|
||||
|
||||
// Check if email confirmation is required
|
||||
const needsVerification = !data.session;
|
||||
|
||||
if (!needsVerification) {
|
||||
user = await getUserFromSession();
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
needsVerification,
|
||||
user: user || undefined
|
||||
};
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Sign up failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send password reset email
|
||||
*/
|
||||
async forgotPassword(email: string): Promise<{ success: boolean; error?: string }> {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const supabase = getSupabaseClient();
|
||||
const redirectTo =
|
||||
options.passwordResetRedirectUrl || `${window.location.origin}/reset-password`;
|
||||
|
||||
const { error: authError } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo
|
||||
});
|
||||
|
||||
if (authError) {
|
||||
error = authError.message;
|
||||
return { success: false, error: authError.message };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Password reset failed';
|
||||
error = errorMessage;
|
||||
return { success: false, error: errorMessage };
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign out user
|
||||
*/
|
||||
async signOut() {
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
const supabase = getSupabaseClient();
|
||||
await supabase.auth.signOut();
|
||||
user = null;
|
||||
} catch (e) {
|
||||
console.error('Sign out failed:', e);
|
||||
error = e instanceof Error ? e.message : 'Sign out failed';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check authentication status
|
||||
*/
|
||||
async checkAuth(): Promise<boolean> {
|
||||
try {
|
||||
const sessionUser = await getUserFromSession();
|
||||
if (!sessionUser) {
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
user = sessionUser;
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Auth check failed:', e);
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear error state
|
||||
*/
|
||||
clearError() {
|
||||
error = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
42
packages/shared-auth-stores/src/index.ts
Normal file
42
packages/shared-auth-stores/src/index.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @manacore/shared-auth-stores
|
||||
*
|
||||
* Svelte 5 auth store factories for the ManaCore monorepo.
|
||||
*
|
||||
* Provides two approaches:
|
||||
* 1. createAuthStore - Generic factory that works with any auth service adapter
|
||||
* 2. createSupabaseAuthStore - Direct Supabase integration for simpler setups
|
||||
*
|
||||
* @example Generic auth store with custom adapter
|
||||
* ```ts
|
||||
* import { createAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { authService } from '$lib/auth';
|
||||
* import type { AppUser } from '$lib/types';
|
||||
*
|
||||
* export const authStore = createAuthStore<AppUser>(authService);
|
||||
* ```
|
||||
*
|
||||
* @example Supabase auth store
|
||||
* ```ts
|
||||
* import { createSupabaseAuthStore } from '@manacore/shared-auth-stores';
|
||||
* import { createBrowserClient } from '@supabase/ssr';
|
||||
*
|
||||
* const getClient = () => createBrowserClient(url, key);
|
||||
* export const authStore = createSupabaseAuthStore(getClient);
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Factory functions
|
||||
export { createAuthStore } from './createAuthStore.svelte';
|
||||
export { createSupabaseAuthStore } from './createSupabaseAuthStore.svelte';
|
||||
export type { CreateSupabaseAuthStoreOptions, SupabaseUser } from './createSupabaseAuthStore.svelte';
|
||||
|
||||
// Types
|
||||
export type {
|
||||
BaseUser,
|
||||
AuthResult,
|
||||
AuthServiceAdapter,
|
||||
AuthStoreState,
|
||||
AuthStoreActions,
|
||||
AuthStore
|
||||
} from './types';
|
||||
94
packages/shared-auth-stores/src/types.ts
Normal file
94
packages/shared-auth-stores/src/types.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Shared Auth Store Types
|
||||
* Generic types for creating auth stores with custom user types
|
||||
*/
|
||||
|
||||
import type { AuthResult as BaseAuthResult } from '@manacore/shared-types';
|
||||
|
||||
/**
|
||||
* Base user interface that all app-specific user types must extend
|
||||
*/
|
||||
export interface BaseUser {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth operation result with typed user
|
||||
*/
|
||||
export interface AuthResult<TUser extends BaseUser = BaseUser> {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
needsVerification?: boolean;
|
||||
user?: TUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth service interface that auth stores expect
|
||||
* Apps implement this to integrate with their auth backend
|
||||
*/
|
||||
export interface AuthServiceAdapter<TUser extends BaseUser = BaseUser> {
|
||||
/** Check if user is authenticated */
|
||||
isAuthenticated(): Promise<boolean>;
|
||||
|
||||
/** Get user data from stored token/session */
|
||||
getUserFromToken(): Promise<TUser | null>;
|
||||
|
||||
/** Sign in with email and password */
|
||||
signIn(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Sign up with email and password */
|
||||
signUp(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Send password reset email */
|
||||
forgotPassword(email: string): Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
/** Sign out user */
|
||||
signOut(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth store state interface
|
||||
*/
|
||||
export interface AuthStoreState<TUser extends BaseUser = BaseUser> {
|
||||
user: TUser | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth store actions interface
|
||||
*/
|
||||
export interface AuthStoreActions<TUser extends BaseUser = BaseUser> {
|
||||
/** Initialize auth state from stored tokens */
|
||||
initialize(): Promise<void>;
|
||||
|
||||
/** Set user manually */
|
||||
setUser(user: TUser | null): void;
|
||||
|
||||
/** Sign in with email and password */
|
||||
signIn(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Sign up with email and password */
|
||||
signUp(email: string, password: string): Promise<AuthResult<TUser>>;
|
||||
|
||||
/** Send password reset email */
|
||||
forgotPassword(email: string): Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
/** Sign out user */
|
||||
signOut(): Promise<void>;
|
||||
|
||||
/** Check authentication status */
|
||||
checkAuth(): Promise<boolean>;
|
||||
|
||||
/** Clear error state */
|
||||
clearError(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete auth store interface
|
||||
*/
|
||||
export interface AuthStore<TUser extends BaseUser = BaseUser>
|
||||
extends AuthStoreState<TUser>,
|
||||
AuthStoreActions<TUser> {}
|
||||
17
packages/shared-auth-stores/tsconfig.json
Normal file
17
packages/shared-auth-stores/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -7,6 +7,65 @@
|
|||
*/
|
||||
export type AuthState = 'loading' | 'authenticated' | 'unauthenticated';
|
||||
|
||||
/**
|
||||
* Standard authentication error codes
|
||||
*/
|
||||
export type AuthErrorCode =
|
||||
| 'INVALID_CREDENTIALS'
|
||||
| 'EMAIL_NOT_CONFIRMED'
|
||||
| 'USER_NOT_FOUND'
|
||||
| 'EMAIL_ALREADY_EXISTS'
|
||||
| 'WEAK_PASSWORD'
|
||||
| 'INVALID_EMAIL'
|
||||
| 'RATE_LIMITED'
|
||||
| 'TOKEN_EXPIRED'
|
||||
| 'TOKEN_INVALID'
|
||||
| 'NETWORK_ERROR'
|
||||
| 'SERVER_ERROR'
|
||||
| 'UNKNOWN_ERROR';
|
||||
|
||||
/**
|
||||
* Structured authentication error
|
||||
*/
|
||||
export interface AuthError {
|
||||
code: AuthErrorCode;
|
||||
message: string;
|
||||
originalError?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map common Supabase error messages to AuthErrorCode
|
||||
*/
|
||||
export function mapSupabaseErrorToCode(message: string): AuthErrorCode {
|
||||
const lowerMessage = message.toLowerCase();
|
||||
|
||||
if (lowerMessage.includes('invalid login credentials')) return 'INVALID_CREDENTIALS';
|
||||
if (lowerMessage.includes('email not confirmed')) return 'EMAIL_NOT_CONFIRMED';
|
||||
if (lowerMessage.includes('user not found')) return 'USER_NOT_FOUND';
|
||||
if (lowerMessage.includes('already registered') || lowerMessage.includes('already exists'))
|
||||
return 'EMAIL_ALREADY_EXISTS';
|
||||
if (lowerMessage.includes('password') && lowerMessage.includes('weak')) return 'WEAK_PASSWORD';
|
||||
if (lowerMessage.includes('invalid email')) return 'INVALID_EMAIL';
|
||||
if (lowerMessage.includes('rate') || lowerMessage.includes('too many')) return 'RATE_LIMITED';
|
||||
if (lowerMessage.includes('token') && lowerMessage.includes('expired')) return 'TOKEN_EXPIRED';
|
||||
if (lowerMessage.includes('token') && lowerMessage.includes('invalid')) return 'TOKEN_INVALID';
|
||||
if (lowerMessage.includes('network') || lowerMessage.includes('fetch')) return 'NETWORK_ERROR';
|
||||
if (lowerMessage.includes('server') || lowerMessage.includes('500')) return 'SERVER_ERROR';
|
||||
|
||||
return 'UNKNOWN_ERROR';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AuthError from a Supabase error message
|
||||
*/
|
||||
export function createAuthError(message: string, originalError?: unknown): AuthError {
|
||||
return {
|
||||
code: mapSupabaseErrorToCode(message),
|
||||
message,
|
||||
originalError
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* User session
|
||||
*/
|
||||
|
|
|
|||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
|
|
@ -1576,6 +1576,22 @@ importers:
|
|||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/shared-auth-stores:
|
||||
dependencies:
|
||||
'@manacore/shared-types':
|
||||
specifier: workspace:*
|
||||
version: link:../shared-types
|
||||
devDependencies:
|
||||
svelte:
|
||||
specifier: ^5.0.0
|
||||
version: 5.43.14
|
||||
svelte-check:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.4(picomatch@4.0.3)(svelte@5.43.14)(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
|
||||
packages/shared-auth-ui:
|
||||
dependencies:
|
||||
'@manacore/shared-auth':
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue