fixed reset password
This commit is contained in:
@@ -3,12 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||
import 'package:flux/core/utils/app_message.dart';
|
||||
import 'package:flux/features/master_data/staff/data/staff_repository.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
part 'auth_state.dart';
|
||||
|
||||
class AuthCubit extends Cubit<AuthState> {
|
||||
final _supabase = GetIt.instance<SupabaseClient>();
|
||||
final _staffRepository = GetIt.instance<StaffRepository>();
|
||||
|
||||
AuthCubit() : super(const AuthState());
|
||||
|
||||
@@ -95,10 +97,7 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
await _supabase.auth.resetPasswordForEmail(
|
||||
email,
|
||||
redirectTo: resetPasswordUrl,
|
||||
);
|
||||
await _staffRepository.resetPassword(email);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: AuthStatus.pwResetSent,
|
||||
|
||||
@@ -128,6 +128,30 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
_submit(), // Se lo supporti nel tuo widget custom
|
||||
),
|
||||
|
||||
// Link "Dimenticato password?" solo in Login mode
|
||||
if (state.isLoginMode) ...[
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () => context
|
||||
.read<AuthCubit>()
|
||||
.requestPasswordReset(
|
||||
_emailController.text.trim(),
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.authScreenForgotPassword,
|
||||
style: TextStyle(
|
||||
color: context.accent,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// --- BOTTONE PRINCIPALE ---
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flux/main.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class SetPasswordScreen extends StatefulWidget {
|
||||
@@ -63,7 +64,7 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
|
||||
setState(() => _isSessionReady = true);
|
||||
}
|
||||
} catch (e) {
|
||||
print("Errore ripristino manuale sessione: $e");
|
||||
debugPrint("Errore ripristino manuale sessione: $e");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +99,7 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
context.go('/');
|
||||
}
|
||||
} on AuthException catch (e) {
|
||||
setState(() {
|
||||
|
||||
@@ -105,12 +105,15 @@ class StaffRepository {
|
||||
}
|
||||
|
||||
Future<void> resetPassword(String email) async {
|
||||
//TODO modificare per fare il reset della password tramite edge function, così da poter personalizzare l'email e il link di reset
|
||||
try {
|
||||
await _supabase.auth.resetPasswordForEmail(
|
||||
email,
|
||||
redirectTo: resetPasswordUrl,
|
||||
final response = await Supabase.instance.client.functions.invoke(
|
||||
'reset_password',
|
||||
body: {'email': email.trim()},
|
||||
);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw Exception(response.data['error'] ?? "Errore sconosciuto");
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception("Errore nell'invio del link: $e");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
import 'package:flux/features/master_data/store/bloc/store_cubit.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class StaffScreen extends StatefulWidget {
|
||||
const StaffScreen({super.key});
|
||||
@@ -193,9 +194,9 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
if (!member.hasJoined) {
|
||||
context.read<StaffCubit>().inviteStaffMember(
|
||||
member: member,
|
||||
selectedStoreIds:
|
||||
member.assignedStores?.map((s) => s.id!).toList() ??
|
||||
[],
|
||||
selectedStoreIds: member.assignedStores
|
||||
.map((s) => s.id!)
|
||||
.toList(),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@@ -206,13 +207,6 @@ class _StaffScreenState extends State<StaffScreen> {
|
||||
);
|
||||
} else {
|
||||
context.read<StaffCubit>().resetPassword(member.email!);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Operazione richiesta, controlla l\'email!',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
@@ -55,9 +54,6 @@ void main() async {
|
||||
final initialUri = Uri.base;
|
||||
if (initialUri.fragment.contains('access_token=')) {
|
||||
initialRecoveryFragment = initialUri.fragment;
|
||||
log(
|
||||
"Ninja attivato: Token catturato in cassaforte! $initialRecoveryFragment",
|
||||
);
|
||||
}
|
||||
await dotenv.load(fileName: ".env");
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: flux
|
||||
description: "Gestione attività negozio di telefonia"
|
||||
publish_to: 'none'
|
||||
version: 1.1.11+29
|
||||
version: 1.1.12+30
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.3
|
||||
|
||||
129
supabase/functions/reset-password/index.ts
Normal file
129
supabase/functions/reset-password/index.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.7.1'
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// 1. GESTIONE CORS
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. ESTRAZIONE EMAIL
|
||||
const { email } = await req.json()
|
||||
if (!email) {
|
||||
throw new Error("Devi fornire un indirizzo email.")
|
||||
}
|
||||
|
||||
// 3. INIZIALIZZAZIONE CLIENT ADMIN
|
||||
const supabaseAdmin = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
|
||||
)
|
||||
|
||||
// 4. RECUPERO NOME UTENTE (Opzionale, ma fa molto pro per l'email)
|
||||
const { data: staffData } = await supabaseAdmin
|
||||
.from('staff_members')
|
||||
.select('name')
|
||||
.eq('email', email)
|
||||
.single()
|
||||
|
||||
const userName = staffData?.name || "Socio"
|
||||
|
||||
// 5. GENERAZIONE DEL LINK DI RECOVERY
|
||||
// Usiamo lo stesso identico URL di redirect che abbiamo blindato prima!
|
||||
const { data: linkData, error: linkError } = await supabaseAdmin.auth.admin.generateLink({
|
||||
type: 'recovery',
|
||||
email: email,
|
||||
options: {
|
||||
redirectTo: 'https://flux.catelli.it/set-password'
|
||||
}
|
||||
})
|
||||
|
||||
if (linkError) {
|
||||
// Se l'utente non esiste su Auth, Supabase lancia un errore.
|
||||
// Per sicurezza (evitare l'enumerazione delle email), restituiamo comunque un 200 finto
|
||||
// oppure intercettiamo l'errore per il debug.
|
||||
console.error("Errore generazione link Supabase:", linkError)
|
||||
throw new Error("Se l'email è registrata, riceverai a breve le istruzioni.")
|
||||
}
|
||||
|
||||
const secureLink = linkData.properties.action_link
|
||||
|
||||
// 6. IL POSTINO RESEND
|
||||
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')
|
||||
if (!RESEND_API_KEY) throw new Error("Chiave API di Resend non configurata.")
|
||||
|
||||
const emailHtml = `
|
||||
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; max-width: 550px; margin: 40px auto; padding: 32px; border: 1px solid #e2e8f0; border-radius: 16px; background-color: #ffffff; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);">
|
||||
|
||||
<div style="margin-bottom: 32px; text-align: left;">
|
||||
<img src="https://flux.catelli.it/assets/flux_logo_dark.svg" width="150" style="display: block; height: auto; border: 0;" alt="FLUX Logo" />
|
||||
</div>
|
||||
|
||||
<h2 style="color: #0f172a; font-size: 22px; font-weight: 700; margin-bottom: 16px; margin-top: 0; letter-spacing: -0.3px;">
|
||||
Ripristino Password
|
||||
</h2>
|
||||
|
||||
<p style="color: #334155; font-size: 15px; line-height: 1.6; margin-bottom: 20px; margin-top: 0;">
|
||||
Ciao <strong>${userName}</strong>,
|
||||
</p>
|
||||
|
||||
<p style="color: #334155; font-size: 15px; line-height: 1.6; margin-bottom: 24px;">
|
||||
Abbiamo ricevuto una richiesta per reimpostare la password del tuo account su FLUX. Clicca sul pulsante qui sotto per sceglierne una nuova.
|
||||
</p>
|
||||
|
||||
<div style="margin: 36px 0; text-align: center;">
|
||||
<a href="${secureLink}" style="background-color: #0f172a; color: #ffffff; padding: 14px 28px; text-decoration: none; border-radius: 8px; font-size: 15px; font-weight: 600; display: inline-block; box-shadow: 0 4px 6px -1px rgba(15, 23, 42, 0.2);">
|
||||
Reimposta la tua Password
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="border-top: 1px solid #f1f5f9; padding-top: 20px; margin-top: 32px;">
|
||||
<p style="color: #94a3b8; font-size: 12px; line-height: 1.5; margin-bottom: 0;">
|
||||
Se non hai richiesto tu il ripristino, puoi ignorare tranquillamente questa email. Il link è strettamente personale e valido per un solo utilizzo.<br><br>
|
||||
<a href="${secureLink}" style="color: #2563eb; text-decoration: none; word-break: break-all;">${secureLink}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Qui puoi cambiare l'indirizzo mittente se vuoi separare gli inviti dal supporto tecnico
|
||||
const resendResponse = await fetch('https://api.resend.com/emails', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${RESEND_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from: 'FLUX Support <support@flux.catelli.it>', // Modificato per sembrare più istituzionale
|
||||
to: [email],
|
||||
subject: 'Recupero Password FLUX',
|
||||
html: emailHtml,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!resendResponse.ok) {
|
||||
const resendError = await resendResponse.text()
|
||||
console.error("Resend ha fallito:", resendError)
|
||||
throw new Error("Errore durante l'invio dell'email.")
|
||||
}
|
||||
|
||||
// 7. TUTTO OK
|
||||
return new Response(
|
||||
JSON.stringify({ success: true, message: "Email inviata con successo." }),
|
||||
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
)
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("CRASH FUNZIONE RESET:", error.message || error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: error.message || "Errore generico del server" }),
|
||||
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user