mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 02:06:41 +02:00
Merge branch 'feat/mana-core'
This commit is contained in:
commit
28d167a978
112 changed files with 34765 additions and 548 deletions
|
|
@ -1,7 +1,10 @@
|
|||
# Supabase Konfiguration
|
||||
# Mana Core Auth Configuration
|
||||
EXPO_PUBLIC_MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
|
||||
# Supabase Configuration (for database only, not auth)
|
||||
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
EXPO_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
|
||||
# Chat Backend API
|
||||
# The backend handles AI API calls securely - no API keys needed in the mobile app
|
||||
EXPO_PUBLIC_BACKEND_URL=http://localhost:3001
|
||||
EXPO_PUBLIC_BACKEND_URL=http://localhost:3002
|
||||
|
|
|
|||
|
|
@ -1,23 +1,99 @@
|
|||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { supabase } from '../utils/supabase';
|
||||
import { Session, User } from '@supabase/supabase-js';
|
||||
import { ActivityIndicator, View, Text } from 'react-native';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import {
|
||||
createAuthService,
|
||||
createTokenManager,
|
||||
setStorageAdapter,
|
||||
setDeviceAdapter,
|
||||
setNetworkAdapter,
|
||||
createMemoryStorageAdapter,
|
||||
type UserData,
|
||||
} from '@manacore/shared-auth';
|
||||
|
||||
// Definiere den Typ für den Auth-Kontext
|
||||
// Mana Core Auth URL from environment
|
||||
const MANA_AUTH_URL = process.env.EXPO_PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
|
||||
|
||||
// Create SecureStore adapter for React Native
|
||||
const createSecureStoreAdapter = () => ({
|
||||
async getItem<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
const value = await SecureStore.getItemAsync(key);
|
||||
return value ? JSON.parse(value) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async setItem(key: string, value: unknown): Promise<void> {
|
||||
await SecureStore.setItemAsync(key, JSON.stringify(value));
|
||||
},
|
||||
async removeItem(key: string): Promise<void> {
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
},
|
||||
});
|
||||
|
||||
// Create device adapter for React Native
|
||||
const createReactNativeDeviceAdapter = () => {
|
||||
let deviceId: string | null = null;
|
||||
|
||||
return {
|
||||
async getDeviceInfo() {
|
||||
if (!deviceId) {
|
||||
// Try to get stored device ID
|
||||
deviceId = await SecureStore.getItemAsync('@device/id');
|
||||
|
||||
if (!deviceId) {
|
||||
// Generate new device ID
|
||||
deviceId = `rn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
await SecureStore.setItemAsync('@device/id', deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId,
|
||||
deviceName: 'React Native Device',
|
||||
platform: 'react-native',
|
||||
};
|
||||
},
|
||||
async getStoredDeviceId() {
|
||||
return deviceId || (await SecureStore.getItemAsync('@device/id'));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Create network adapter (basic implementation)
|
||||
const createReactNativeNetworkAdapter = () => ({
|
||||
async isConnected() {
|
||||
return true; // Always assume connected for now
|
||||
},
|
||||
async hasStableConnection() {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize adapters
|
||||
setStorageAdapter(createSecureStoreAdapter());
|
||||
setDeviceAdapter(createReactNativeDeviceAdapter());
|
||||
setNetworkAdapter(createReactNativeNetworkAdapter());
|
||||
|
||||
// Create auth service
|
||||
const authService = createAuthService({ baseUrl: MANA_AUTH_URL });
|
||||
const tokenManager = createTokenManager(authService);
|
||||
|
||||
// Auth context type
|
||||
type AuthContextType = {
|
||||
session: Session | null;
|
||||
user: User | null;
|
||||
user: UserData | null;
|
||||
loading: boolean;
|
||||
signIn: (email: string, password: string) => Promise<{ error: any | null }>;
|
||||
signUp: (email: string, password: string) => Promise<{ error: any | null, data: any | null }>;
|
||||
signUp: (email: string, password: string) => Promise<{ error: any | null; data: any | null }>;
|
||||
signOut: () => Promise<void>;
|
||||
resetPassword: (email: string) => Promise<{ error: any | null }>;
|
||||
};
|
||||
|
||||
// Erstelle den Auth-Kontext
|
||||
// Create auth context
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
// Hook für den Zugriff auf den Auth-Kontext
|
||||
// Hook to access auth context
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
|
|
@ -26,57 +102,51 @@ export const useAuth = () => {
|
|||
return context;
|
||||
};
|
||||
|
||||
// AuthProvider-Komponente
|
||||
// AuthProvider component
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [user, setUser] = useState<UserData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Initialisiere den Auth-Status
|
||||
// Initialize auth state
|
||||
useEffect(() => {
|
||||
// Hole die aktuelle Session
|
||||
const getInitialSession = async () => {
|
||||
const initialize = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Prüfe, ob bereits eine Session existiert
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
|
||||
// Abonniere Änderungen am Auth-Status
|
||||
const { data: { subscription } } = await supabase.auth.onAuthStateChange(
|
||||
(_event, session) => {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
|
||||
// Check if user is authenticated
|
||||
const authenticated = await authService.isAuthenticated();
|
||||
|
||||
if (authenticated) {
|
||||
const userData = await authService.getUserFromToken();
|
||||
setUser(userData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Initialisieren der Auth-Session:', error);
|
||||
setUser(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
getInitialSession();
|
||||
|
||||
initialize();
|
||||
}, []);
|
||||
|
||||
// Anmelden mit E-Mail und Passwort
|
||||
// Sign in with email and password
|
||||
const signIn = async (email: string, password: string) => {
|
||||
try {
|
||||
console.log('Versuche Anmeldung mit:', email);
|
||||
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
|
||||
|
||||
if (error) {
|
||||
console.error('Supabase Auth Fehler:', error.message, error.status);
|
||||
return { error };
|
||||
const result = await authService.signIn(email, password);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Auth Fehler:', result.error);
|
||||
return { error: { message: result.error } };
|
||||
}
|
||||
|
||||
console.log('Anmeldung erfolgreich:', data.user?.id);
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
setUser(userData);
|
||||
|
||||
console.log('Anmeldung erfolgreich:', userData?.userId);
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
console.error('Unerwarteter Fehler beim Anmelden:', error.message || error);
|
||||
|
|
@ -84,55 +154,56 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
};
|
||||
|
||||
// Registrieren mit E-Mail und Passwort
|
||||
// Sign up with email and password
|
||||
const signUp = async (email: string, password: string) => {
|
||||
try {
|
||||
// Registriere den Benutzer mit autoConfirm=true, um die E-Mail-Bestätigung zu umgehen
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
email_confirmed: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!error && data?.user) {
|
||||
// Wenn die Registrierung erfolgreich war, melde den Benutzer direkt an
|
||||
await signIn(email, password);
|
||||
const result = await authService.signUp(email, password);
|
||||
|
||||
if (!result.success) {
|
||||
return { data: null, error: { message: result.error } };
|
||||
}
|
||||
|
||||
return { data, error };
|
||||
|
||||
// Auto sign in after successful signup
|
||||
const signInResult = await signIn(email, password);
|
||||
|
||||
if (signInResult.error) {
|
||||
return { data: null, error: signInResult.error };
|
||||
}
|
||||
|
||||
return { data: user, error: null };
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Registrieren:', error);
|
||||
return { data: null, error };
|
||||
}
|
||||
};
|
||||
|
||||
// Abmelden
|
||||
// Sign out
|
||||
const signOut = async () => {
|
||||
try {
|
||||
await supabase.auth.signOut();
|
||||
await authService.signOut();
|
||||
setUser(null);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abmelden:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Passwort zurücksetzen
|
||||
// Reset password
|
||||
const resetPassword = async (email: string) => {
|
||||
try {
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: 'exp://localhost:8081/reset-password',
|
||||
});
|
||||
return { error };
|
||||
const result = await authService.forgotPassword(email);
|
||||
|
||||
if (!result.success) {
|
||||
return { error: { message: result.error } };
|
||||
}
|
||||
|
||||
return { error: null };
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Zurücksetzen des Passworts:', error);
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
|
||||
// Zeige Ladeindikator während der Initialisierung
|
||||
// Show loading indicator during initialization
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
|
|
@ -142,11 +213,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
);
|
||||
}
|
||||
|
||||
// Stelle den Auth-Kontext bereit
|
||||
// Provide auth context
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
session,
|
||||
user,
|
||||
loading,
|
||||
signIn,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
# Supabase Configuration (same as mobile app)
|
||||
# Mana Core Auth Configuration
|
||||
PUBLIC_MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
|
||||
# Supabase Configuration (for database only, not auth)
|
||||
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
|
||||
# Chat Backend API
|
||||
PUBLIC_BACKEND_URL=http://localhost:3001
|
||||
PUBLIC_BACKEND_URL=http://localhost:3002
|
||||
|
|
|
|||
|
|
@ -1,32 +1,24 @@
|
|||
/**
|
||||
* Auth Store - Manages authentication state using Svelte 5 runes
|
||||
* Compatible with Chat mobile app (same Supabase instance)
|
||||
* Now using Mana Core Auth instead of Supabase Auth
|
||||
*/
|
||||
|
||||
import { createSupabaseBrowserClient } from '$lib/services/supabase';
|
||||
import type { Session, User } from '@supabase/supabase-js';
|
||||
import { initializeWebAuth, type UserData } from '@manacore/shared-auth';
|
||||
import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
|
||||
|
||||
// Initialize Mana Core Auth
|
||||
const MANA_AUTH_URL = PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
|
||||
const { authService, tokenManager } = initializeWebAuth({
|
||||
baseUrl: MANA_AUTH_URL,
|
||||
});
|
||||
|
||||
// State
|
||||
let session = $state<Session | null>(null);
|
||||
let user = $state<User | null>(null);
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
let initialized = $state(false);
|
||||
|
||||
// Create browser client
|
||||
let supabase: ReturnType<typeof createSupabaseBrowserClient> | null = null;
|
||||
|
||||
function getSupabase() {
|
||||
if (!supabase) {
|
||||
supabase = createSupabaseBrowserClient();
|
||||
}
|
||||
return supabase;
|
||||
}
|
||||
|
||||
export const authStore = {
|
||||
// Getters
|
||||
get session() {
|
||||
return session;
|
||||
},
|
||||
get user() {
|
||||
return user;
|
||||
},
|
||||
|
|
@ -41,33 +33,21 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Initialize auth state from Supabase session
|
||||
* Initialize auth state from stored tokens
|
||||
*/
|
||||
async initialize() {
|
||||
if (initialized) return;
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
const sb = getSupabase();
|
||||
|
||||
// Get current session
|
||||
const {
|
||||
data: { session: currentSession },
|
||||
} = await sb.auth.getSession();
|
||||
|
||||
session = currentSession;
|
||||
user = currentSession?.user ?? null;
|
||||
|
||||
// Subscribe to auth changes
|
||||
sb.auth.onAuthStateChange((_event, newSession) => {
|
||||
session = newSession;
|
||||
user = newSession?.user ?? null;
|
||||
});
|
||||
|
||||
const authenticated = await authService.isAuthenticated();
|
||||
if (authenticated) {
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
}
|
||||
initialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize auth:', error);
|
||||
session = null;
|
||||
user = null;
|
||||
} finally {
|
||||
loading = false;
|
||||
|
|
@ -78,83 +58,98 @@ export const authStore = {
|
|||
* Sign in with email and password
|
||||
*/
|
||||
async signIn(email: string, password: string) {
|
||||
const sb = getSupabase();
|
||||
const { data, error } = await sb.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
try {
|
||||
const result = await authService.signIn(email, password);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message };
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Login failed' };
|
||||
}
|
||||
|
||||
// Get user data from token
|
||||
const userData = await authService.getUserFromToken();
|
||||
user = userData;
|
||||
|
||||
return { success: true, error: null };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
|
||||
session = data.session;
|
||||
user = data.user;
|
||||
return { success: true, error: null };
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign up with email and password
|
||||
*/
|
||||
async signUp(email: string, password: string) {
|
||||
const sb = getSupabase();
|
||||
const { data, error } = await sb.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
email_confirmed: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
const result = await authService.signUp(email, password);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, needsVerification: false };
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
|
||||
}
|
||||
|
||||
// Mana Core Auth requires separate login after signup
|
||||
if (result.needsVerification) {
|
||||
return { success: true, error: null, needsVerification: true };
|
||||
}
|
||||
|
||||
// Auto sign in after successful signup
|
||||
const signInResult = await this.signIn(email, password);
|
||||
return { ...signInResult, needsVerification: false };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage, needsVerification: false };
|
||||
}
|
||||
|
||||
// Check if email confirmation is required
|
||||
if (data.user && !data.session) {
|
||||
return { success: true, error: null, needsVerification: true };
|
||||
}
|
||||
|
||||
session = data.session;
|
||||
user = data.user;
|
||||
return { success: true, error: null, needsVerification: false };
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign out
|
||||
*/
|
||||
async signOut() {
|
||||
const sb = getSupabase();
|
||||
await sb.auth.signOut();
|
||||
session = null;
|
||||
user = null;
|
||||
try {
|
||||
await authService.signOut();
|
||||
user = null;
|
||||
} catch (error) {
|
||||
console.error('Sign out error:', error);
|
||||
// Clear user even if sign out fails
|
||||
user = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send password reset email
|
||||
*/
|
||||
async resetPassword(email: string) {
|
||||
const sb = getSupabase();
|
||||
const { error } = await sb.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${window.location.origin}/auth/reset-password`,
|
||||
});
|
||||
try {
|
||||
const result = await authService.forgotPassword(email);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message };
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error || 'Password reset failed' };
|
||||
}
|
||||
|
||||
return { success: true, error: null };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
|
||||
return { success: true, error: null };
|
||||
},
|
||||
|
||||
/**
|
||||
* Set session from server-side data
|
||||
* Get user credit balance
|
||||
*/
|
||||
setSession(newSession: Session | null) {
|
||||
session = newSession;
|
||||
user = newSession?.user ?? null;
|
||||
initialized = true;
|
||||
loading = false;
|
||||
async getCredits() {
|
||||
try {
|
||||
const credits = await authService.getUserCredits();
|
||||
return credits;
|
||||
} catch (error) {
|
||||
console.error('Failed to get credits:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
*/
|
||||
async getAccessToken() {
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue