feat: implement user authentication with login and register
- Add login screen with email/username support - Add register screen with email validation - Implement AuthStore with expo-secure-store (native) / localStorage (web) - Add X-User-Id header authentication (simple auth without JWT) - Rename displayName to userName across codebase - Add findByUserName() to UserRepository - Check for existing email AND username on registration - Add AuthButton component with shadow effect - Add logout button to Header - Add hash-password.js utility script for manual password resets - Update CORS to allow X-User-Id header
This commit is contained in:
@@ -1,26 +1,69 @@
|
||||
import { create } from "zustand";
|
||||
import { Platform } from "react-native";
|
||||
import { User } from "@caldav/shared";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
|
||||
const USER_STORAGE_KEY = "auth_user";
|
||||
|
||||
// SecureStore doesn't work on web, use localStorage as fallback
|
||||
const storage = {
|
||||
async setItem(key: string, value: string): Promise<void> {
|
||||
if (Platform.OS === "web") {
|
||||
localStorage.setItem(key, value);
|
||||
} else {
|
||||
await SecureStore.setItemAsync(key, value);
|
||||
}
|
||||
},
|
||||
async getItem(key: string): Promise<string | null> {
|
||||
if (Platform.OS === "web") {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
return SecureStore.getItemAsync(key);
|
||||
},
|
||||
async deleteItem(key: string): Promise<void> {
|
||||
if (Platform.OS === "web") {
|
||||
localStorage.removeItem(key);
|
||||
} else {
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
isAuthenticated: boolean;
|
||||
login: (user: User, token: string) => void;
|
||||
logout: () => void;
|
||||
setToken: (token: string) => void;
|
||||
isLoading: boolean;
|
||||
login: (user: User) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
loadStoredUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
token: null,
|
||||
isAuthenticated: false,
|
||||
login: (_user: User, _token: string) => {
|
||||
throw new Error("Not implemented");
|
||||
isLoading: true,
|
||||
|
||||
login: async (user: User) => {
|
||||
await storage.setItem(USER_STORAGE_KEY, JSON.stringify(user));
|
||||
set({ user, isAuthenticated: true });
|
||||
},
|
||||
logout: () => {
|
||||
throw new Error("Not implemented");
|
||||
|
||||
logout: async () => {
|
||||
await storage.deleteItem(USER_STORAGE_KEY);
|
||||
set({ user: null, isAuthenticated: false });
|
||||
},
|
||||
setToken: (_token: string) => {
|
||||
throw new Error("Not implemented");
|
||||
|
||||
loadStoredUser: async () => {
|
||||
try {
|
||||
const stored = await storage.getItem(USER_STORAGE_KEY);
|
||||
if (stored) {
|
||||
const user = JSON.parse(stored) as User;
|
||||
set({ user, isAuthenticated: true, isLoading: false });
|
||||
} else {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
} catch {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user