288 lines
7.6 KiB
TypeScript
288 lines
7.6 KiB
TypeScript
|
|
interface User {
|
|
id: number
|
|
username: string
|
|
email: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
role: {
|
|
code: string
|
|
name: { th: string; en: string }
|
|
}
|
|
profile?: {
|
|
prefix: { th: string; en: string }
|
|
first_name: string
|
|
last_name: string
|
|
phone: string | null
|
|
avatar_url: string | null
|
|
}
|
|
}
|
|
|
|
interface loginResponse {
|
|
token: string
|
|
refreshToken: string
|
|
user: User
|
|
profile: User['profile']
|
|
}
|
|
|
|
interface RegisterPayload {
|
|
username: string
|
|
email: string
|
|
password: string
|
|
first_name: string
|
|
last_name: string
|
|
prefix: { th: string; en: string }
|
|
phone: string
|
|
}
|
|
|
|
export const useAuth = () => {
|
|
const config = useRuntimeConfig()
|
|
const API_BASE_URL = config.public.apiBase as string
|
|
const token = useCookie('auth_token', {
|
|
maxAge: 60 * 60 * 24, // 1 day
|
|
sameSite: 'lax',
|
|
secure: false // Set to true in production with HTTPS
|
|
})
|
|
|
|
const user = useCookie<User | null>('auth_user_data', {
|
|
maxAge: 60 * 60 * 24 * 7, // 1 week
|
|
sameSite: 'lax',
|
|
secure: false
|
|
})
|
|
|
|
const isAuthenticated = computed(() => !!token.value)
|
|
|
|
// Refresh Token Logic
|
|
const refreshToken = useCookie('auth_refresh_token', {
|
|
maxAge: 60 * 60 * 24 * 7, // 7 days (matching API likely)
|
|
sameSite: 'lax',
|
|
secure: false
|
|
})
|
|
|
|
|
|
|
|
const login = async (credentials: { email: string; password: string }) => {
|
|
try {
|
|
const data = await $fetch<loginResponse>(`${API_BASE_URL}/auth/login`, {
|
|
method: 'POST',
|
|
body: credentials
|
|
})
|
|
|
|
if (data) {
|
|
// Validation: Only allow STUDENT role to login
|
|
if (data.user.role.code !== 'STUDENT') {
|
|
return { success: false, error: 'Email ไม่ถูกต้อง' }
|
|
}
|
|
|
|
token.value = data.token
|
|
refreshToken.value = data.refreshToken // Save refresh token
|
|
|
|
// The API returns the profile nested inside the user object
|
|
user.value = data.user
|
|
|
|
return { success: true }
|
|
}
|
|
return { success: false, error: 'No data returned' }
|
|
} catch (err: any) {
|
|
console.error('Login failed:', err)
|
|
return {
|
|
success: false,
|
|
error: err.data?.message || err.message || 'เข้าสู่ระบบไม่สำเร็จ'
|
|
}
|
|
}
|
|
}
|
|
|
|
const register = async (payload: RegisterPayload) => {
|
|
try {
|
|
const data = await $fetch(`${API_BASE_URL}/auth/register-learner`, {
|
|
method: 'POST',
|
|
body: payload
|
|
})
|
|
|
|
return { success: true, data }
|
|
|
|
} catch (err: any) {
|
|
console.error('Register failed:', err)
|
|
return {
|
|
success: false,
|
|
error: err.data?.error?.message || err.data?.message || err.message || 'ลงทะเบียนไม่สำเร็จ'
|
|
}
|
|
}
|
|
}
|
|
|
|
const fetchUserProfile = async () => {
|
|
if (!token.value) return
|
|
|
|
try {
|
|
const data = await $fetch<User>(`${API_BASE_URL}/user/me`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token.value}`
|
|
}
|
|
})
|
|
|
|
if (data) {
|
|
user.value = data
|
|
}
|
|
} catch (error: any) {
|
|
if (error.statusCode === 401) {
|
|
// Try to refresh token
|
|
const refreshed = await refreshAccessToken()
|
|
if (refreshed) {
|
|
// Retry fetch with new token
|
|
try {
|
|
const retryData = await $fetch<User>(`${API_BASE_URL}/user/me`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token.value}`
|
|
}
|
|
})
|
|
|
|
if (retryData) {
|
|
user.value = retryData
|
|
return
|
|
}
|
|
} catch (retryErr) {
|
|
console.error('Failed to fetch user profile after refresh:', retryErr)
|
|
}
|
|
} else {
|
|
logout()
|
|
}
|
|
} else {
|
|
console.error('Failed to fetch user profile:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
const updateUserProfile = async (payload: {
|
|
first_name: string
|
|
last_name: string
|
|
phone: string
|
|
prefix: { th: string; en: string }
|
|
}) => {
|
|
if (!token.value) return
|
|
|
|
try {
|
|
await $fetch(`${API_BASE_URL}/user/me`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
Authorization: `Bearer ${token.value}`
|
|
},
|
|
body: payload
|
|
})
|
|
|
|
// If successful, refresh the local user data
|
|
await fetchUserProfile()
|
|
|
|
return { success: true }
|
|
} catch (err: any) {
|
|
console.error('Failed to update profile:', err)
|
|
return { success: false, error: err.data?.message || err.message || 'บันทึกข้อมูลไม่สำเร็จ' }
|
|
}
|
|
}
|
|
|
|
const requestPasswordReset = async (email: string) => {
|
|
try {
|
|
await $fetch(`${API_BASE_URL}/auth/reset-request`, {
|
|
method: 'POST',
|
|
body: { email }
|
|
})
|
|
|
|
return { success: true }
|
|
} catch (err: any) {
|
|
return { success: false, error: err.data?.message || 'ส่งคำขอไม่สำเร็จ' }
|
|
}
|
|
}
|
|
|
|
const confirmResetPassword = async (payload: { token: string; password: string }) => {
|
|
try {
|
|
await $fetch(`${API_BASE_URL}/auth/reset-password`, {
|
|
method: 'POST',
|
|
body: payload
|
|
})
|
|
|
|
return { success: true }
|
|
} catch (err: any) {
|
|
return { success: false, error: err.data?.message || 'รีเซ็ตรหัสผ่านไม่สำเร็จ' }
|
|
}
|
|
}
|
|
|
|
const changePassword = async (payload: { oldPassword: string, newPassword: string }) => {
|
|
if (!token.value) return { success: false, error: 'ไม่พบ Token การใช้งาน' }
|
|
|
|
try {
|
|
await $fetch(`${API_BASE_URL}/user/change-password`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token.value}`
|
|
},
|
|
body: payload
|
|
})
|
|
|
|
return { success: true }
|
|
} catch (err: any) {
|
|
return { success: false, error: err.data?.message || 'เปลี่ยนรหัสผ่านไม่สำเร็จ' }
|
|
}
|
|
}
|
|
|
|
const refreshAccessToken = async () => {
|
|
if (!refreshToken.value) return false
|
|
|
|
try {
|
|
const data = await $fetch<{ token: string; refreshToken: string }>(`${API_BASE_URL}/auth/refresh`, {
|
|
method: 'POST',
|
|
body: { refreshToken: refreshToken.value }
|
|
})
|
|
|
|
if (data) {
|
|
token.value = data.token
|
|
refreshToken.value = data.refreshToken
|
|
return true
|
|
}
|
|
} catch (err) {
|
|
// Refresh failed, force logout
|
|
logout()
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
const logout = () => {
|
|
token.value = null
|
|
refreshToken.value = null // Clear refresh token
|
|
user.value = null
|
|
const router = useRouter()
|
|
router.push('/auth/login')
|
|
}
|
|
|
|
return {
|
|
isAuthenticated,
|
|
token,
|
|
user,
|
|
currentUser: computed(() => {
|
|
if (!user.value) return null
|
|
|
|
const prefix = user.value.profile?.prefix?.th || ''
|
|
const firstName = user.value.profile?.first_name || user.value.username
|
|
const lastName = user.value.profile?.last_name || ''
|
|
|
|
return {
|
|
prefix: user.value.profile?.prefix || { th: '', en: '' },
|
|
firstName,
|
|
lastName,
|
|
email: user.value.email,
|
|
phone: user.value.profile?.phone || '',
|
|
photoURL: user.value.profile?.avatar_url || '',
|
|
role: user.value.role,
|
|
createdAt: user.value.created_at || new Date().toISOString()
|
|
}
|
|
}),
|
|
login,
|
|
register,
|
|
fetchUserProfile,
|
|
updateUserProfile,
|
|
requestPasswordReset,
|
|
confirmResetPassword,
|
|
changePassword,
|
|
refreshAccessToken,
|
|
logout
|
|
}
|
|
}
|