2026.06.05 frontend javítgatás és új belépési logika megvalósítva
This commit is contained in:
@@ -3,6 +3,34 @@ import { ref, computed } from 'vue'
|
||||
import router from '../router'
|
||||
import api from '../api/axios'
|
||||
|
||||
export interface AddressData {
|
||||
zip: string | null
|
||||
city: string | null
|
||||
street_name: string | null
|
||||
street_type: string | null
|
||||
house_number: string | null
|
||||
stairwell: string | null
|
||||
floor: string | null
|
||||
door: string | null
|
||||
parcel_id: string | null
|
||||
full_address_text: string | null
|
||||
}
|
||||
|
||||
export interface PersonData {
|
||||
id: number
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
phone: string | null
|
||||
mothers_last_name: string | null
|
||||
mothers_first_name: string | null
|
||||
birth_place: string | null
|
||||
birth_date: string | null
|
||||
identity_docs: Record<string, any> | null
|
||||
ice_contact: Record<string, any> | null
|
||||
is_active: boolean
|
||||
address: AddressData | null
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
id: number
|
||||
email: string
|
||||
@@ -16,8 +44,11 @@ export interface UserProfile {
|
||||
scope_id: string | null
|
||||
ui_mode: string
|
||||
active_organization_id: number | null
|
||||
preferred_language: string
|
||||
// person_id is set when KYC is complete (Person record exists)
|
||||
person_id: number | null
|
||||
// Nested person data with address
|
||||
person: PersonData | null
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
@@ -42,14 +73,64 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
})
|
||||
const userId = computed(() => user.value?.id ?? null)
|
||||
|
||||
// ────────────────────────────── Helpers ────────────────────────────
|
||||
|
||||
/**
|
||||
* Extract the raw error code from the backend response.
|
||||
* Backend returns codes like "[AUTH.INVALID_CREDENTIALS]" or plain strings.
|
||||
* Strips brackets and returns the clean code, or null if not a code.
|
||||
*/
|
||||
function extractErrorCode(err: any): string | null {
|
||||
const detail = err.response?.data?.detail || err.response?.data?.message || ''
|
||||
if (!detail) return null
|
||||
// Strip surrounding brackets: [AUTH.INVALID_CREDENTIALS] -> AUTH.INVALID_CREDENTIALS
|
||||
const match = detail.match(/^\[?([A-Z]+\.[A-Z_]+)\]?$/)
|
||||
return match ? match[1] : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a backend error code to a user-friendly i18n key path.
|
||||
* Returns the i18n key (e.g. 'auth.invalid_credentials') or null if unmapped.
|
||||
*/
|
||||
function mapErrorCodeToI18nKey(code: string): string | null {
|
||||
const map: Record<string, string> = {
|
||||
'AUTH.INVALID_CREDENTIALS': 'auth.invalid_credentials',
|
||||
'AUTH.USER_INACTIVE': 'auth.user_inactive',
|
||||
'AUTH.USER_NOT_FOUND': 'auth.invalid_credentials',
|
||||
'AUTH.EMAIL_EXISTS': 'auth.email_exists',
|
||||
'AUTH.INVALID_TOKEN': 'auth.invalid_token',
|
||||
'AUTH.TOKEN_EXPIRED': 'auth.token_expired',
|
||||
'AUTH.RATE_LIMIT': 'auth.rate_limit',
|
||||
}
|
||||
return map[code] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-friendly error message from an API error response.
|
||||
* Returns an i18n key path (e.g. 'auth.invalid_credentials') if the error
|
||||
* matches a known code, or the raw detail string as fallback.
|
||||
*/
|
||||
function getErrorMessage(err: any, fallback: string): string {
|
||||
const code = extractErrorCode(err)
|
||||
if (code) {
|
||||
const i18nKey = mapErrorCodeToI18nKey(code)
|
||||
if (i18nKey) return i18nKey
|
||||
}
|
||||
// Fallback to raw detail or message
|
||||
return err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
fallback
|
||||
}
|
||||
|
||||
// ────────────────────────────── Actions ────────────────────────────
|
||||
|
||||
/**
|
||||
* Login with email + password.
|
||||
* The FastAPI /auth/login endpoint uses OAuth2PasswordRequestForm,
|
||||
* which expects application/x-www-form-urlencoded with `username` and `password`.
|
||||
* Supports optional device_fingerprint for Device-Hub tracking.
|
||||
*/
|
||||
async function login(email: string, password: string) {
|
||||
async function login(email: string, password: string, deviceFingerprint?: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
@@ -57,6 +138,9 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const body = new URLSearchParams()
|
||||
body.append('username', email) // FastAPI OAuth2 form uses `username` key
|
||||
body.append('password', password)
|
||||
if (deviceFingerprint) {
|
||||
body.append('device_fingerprint', deviceFingerprint)
|
||||
}
|
||||
|
||||
const res = await api.post('/auth/login', body, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
@@ -78,12 +162,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
// Immediately fetch the user profile
|
||||
await fetchUser()
|
||||
|
||||
// Clear pending verification email after successful login
|
||||
localStorage.removeItem('pending_verification_email')
|
||||
|
||||
// If KYC is not complete, redirect to KYC page
|
||||
if (!isKycComplete.value) {
|
||||
router.push('/complete-kyc')
|
||||
}
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Bejelentkezés sikertelen. Ellenőrizd az adataidat.'
|
||||
error.value = message
|
||||
error.value = getErrorMessage(err, 'auth.loginFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -132,6 +220,9 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
const res = await api.post('/auth/register', payload)
|
||||
|
||||
// Save email to localStorage for verification page auto-fill
|
||||
localStorage.setItem('pending_verification_email', data.email)
|
||||
|
||||
return res.data as {
|
||||
status: string
|
||||
message: string
|
||||
@@ -139,11 +230,26 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
email: string
|
||||
}
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Regisztráció sikertelen.'
|
||||
error.value = message
|
||||
error.value = getErrorMessage(err, 'auth.registerFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend the verification email.
|
||||
* POST /auth/resend-verification with { email }.
|
||||
*/
|
||||
async function resendVerification(email: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.post('/auth/resend-verification', { email })
|
||||
return res.data as { status: string; message: string }
|
||||
} catch (err: any) {
|
||||
error.value = getErrorMessage(err, 'auth.resendFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -161,11 +267,35 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const res = await api.post('/auth/forgot-password', { email })
|
||||
return res.data as { status: string; message: string }
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Hiba történt a jelszó-visszaállítási kérelem elküldésekor.'
|
||||
error.value = message
|
||||
const code = extractErrorCode(err)
|
||||
if (code === 'AUTH.USER_INACTIVE') {
|
||||
error.value = 'auth.user_inactive'
|
||||
} else {
|
||||
error.value = getErrorMessage(err, 'auth.forgotFailed')
|
||||
}
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset password using token from email.
|
||||
* POST /auth/reset-password with { email, token, new_password }.
|
||||
*/
|
||||
async function resetPassword(email: string, token: string, newPassword: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.post('/auth/reset-password', {
|
||||
email,
|
||||
token,
|
||||
new_password: newPassword,
|
||||
})
|
||||
return res.data as { status: string; message: string }
|
||||
} catch (err: any) {
|
||||
error.value = getErrorMessage(err, 'auth.resetPasswordFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -198,6 +328,56 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify email account using the token from the activation link (Magic Link).
|
||||
* POST /auth/verify-email with { token, device_fingerprint }.
|
||||
* Now returns JWT tokens on success, automatically logging the user in.
|
||||
* Supports optional device_fingerprint for Device-Hub tracking.
|
||||
*/
|
||||
async function verifyAccount(verificationToken: string, deviceFingerprint?: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const payload: Record<string, any> = { token: verificationToken }
|
||||
if (deviceFingerprint) {
|
||||
payload.device_fingerprint = deviceFingerprint
|
||||
}
|
||||
const res = await api.post('/auth/verify-email', payload)
|
||||
const data = res.data as {
|
||||
access_token?: string
|
||||
refresh_token?: string
|
||||
token_type?: string
|
||||
is_active?: boolean
|
||||
status?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
// Magic Link: Ha a backend JWT tokent küld vissza, automatikus bejelentkezés
|
||||
if (data.access_token) {
|
||||
// Persist token
|
||||
localStorage.setItem('access_token', data.access_token)
|
||||
if (data.refresh_token) {
|
||||
localStorage.setItem('refresh_token', data.refresh_token)
|
||||
}
|
||||
token.value = data.access_token
|
||||
|
||||
// Immediately fetch the user profile
|
||||
await fetchUser()
|
||||
|
||||
// Clear pending verification email after successful verification + login
|
||||
localStorage.removeItem('pending_verification_email')
|
||||
}
|
||||
|
||||
return data
|
||||
} catch (err: any) {
|
||||
error.value = getErrorMessage(err, 'auth.verificationFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete KYC (Know Your Customer) — full profile submission.
|
||||
* POSTs the KYC data to /auth/complete-kyc, then refreshes the user profile.
|
||||
@@ -213,11 +393,52 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
await fetchUser()
|
||||
return true
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'A KYC kitöltése sikertelen. Kérlek ellenőrizd az adatokat.'
|
||||
error.value = message
|
||||
error.value = getErrorMessage(err, 'auth.kycFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's Person record (profile data + address).
|
||||
* PUT /users/me/person with PersonUpdate schema.
|
||||
* On success, refreshes the local user state with the response.
|
||||
*/
|
||||
async function updatePerson(personData: Record<string, any>): Promise<boolean> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.put('/users/me/person', personData)
|
||||
// Update the local user state with the fresh response
|
||||
user.value = res.data as UserProfile
|
||||
return true
|
||||
} catch (err: any) {
|
||||
error.value = getErrorMessage(err, 'auth.kycFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current user's password.
|
||||
* PUT /users/me/password with { current_password, new_password }.
|
||||
* On success, returns { status: 'ok', message: string }.
|
||||
*/
|
||||
async function changePassword(currentPassword: string, newPassword: string): Promise<{ status: string; message: string }> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.put('/users/me/password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword,
|
||||
})
|
||||
return res.data as { status: string; message: string }
|
||||
} catch (err: any) {
|
||||
error.value = getErrorMessage(err, 'A jelszó módosítása sikertelen.')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -249,8 +470,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
login,
|
||||
fetchUser,
|
||||
register,
|
||||
resendVerification,
|
||||
verifyAccount,
|
||||
forgotPassword,
|
||||
resetPassword,
|
||||
completeKyc,
|
||||
updatePerson,
|
||||
changePassword,
|
||||
logout,
|
||||
init,
|
||||
clearError,
|
||||
|
||||
Reference in New Issue
Block a user