Users can register, log in, and manage profile/password in a personal account page; server data is scoped by owner across contacts, maps, tags, and import. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
3.3 KiB
JavaScript
127 lines
3.3 KiB
JavaScript
import { defineStore } from 'pinia'
|
|
import api from '../api'
|
|
|
|
const TOKEN_KEY = 'sg-access-token'
|
|
const REFRESH_KEY = 'sg-refresh-token'
|
|
const USER_KEY = 'sg-user'
|
|
|
|
function readUser() {
|
|
try {
|
|
const raw = localStorage.getItem(USER_KEY)
|
|
return raw ? JSON.parse(raw) : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function persistSession({ access, refresh, user }) {
|
|
if (access) localStorage.setItem(TOKEN_KEY, access)
|
|
if (refresh) localStorage.setItem(REFRESH_KEY, refresh)
|
|
if (user) localStorage.setItem(USER_KEY, JSON.stringify(user))
|
|
}
|
|
|
|
function clearSessionStorage() {
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
localStorage.removeItem(REFRESH_KEY)
|
|
localStorage.removeItem(USER_KEY)
|
|
}
|
|
|
|
export const useAuthStore = defineStore('auth', {
|
|
state: () => ({
|
|
accessToken: localStorage.getItem(TOKEN_KEY) || '',
|
|
refreshToken: localStorage.getItem(REFRESH_KEY) || '',
|
|
user: readUser(),
|
|
}),
|
|
|
|
getters: {
|
|
isAuthenticated: (state) => Boolean(state.accessToken),
|
|
username: (state) => state.user?.username || '',
|
|
userId: (state) => state.user?.id ?? null,
|
|
},
|
|
|
|
actions: {
|
|
setSession({ access, refresh, user }) {
|
|
this.accessToken = access || ''
|
|
this.refreshToken = refresh || ''
|
|
this.user = user || null
|
|
persistSession({ access, refresh, user })
|
|
},
|
|
|
|
async register({ username, email, password }) {
|
|
const { data } = await api.post('/v1/auth/register/', {
|
|
username: username.trim(),
|
|
email: (email || '').trim(),
|
|
password,
|
|
})
|
|
this.setSession(data)
|
|
return data
|
|
},
|
|
|
|
async login({ username, password }) {
|
|
const { data } = await api.post('/v1/auth/token/', {
|
|
username: username.trim(),
|
|
password,
|
|
})
|
|
this.setSession({
|
|
access: data.access,
|
|
refresh: data.refresh,
|
|
user: { username: username.trim() },
|
|
})
|
|
await this.fetchMe()
|
|
return data
|
|
},
|
|
|
|
async fetchMe() {
|
|
const { data } = await api.get('/v1/auth/me/')
|
|
this.user = data
|
|
localStorage.setItem(USER_KEY, JSON.stringify(data))
|
|
return data
|
|
},
|
|
|
|
async updateProfile({ username, email, currentPassword }) {
|
|
const payload = { current_password: currentPassword }
|
|
if (username !== undefined) payload.username = username.trim()
|
|
if (email !== undefined) payload.email = (email || '').trim()
|
|
const { data } = await api.patch('/v1/auth/me/', payload)
|
|
this.user = data
|
|
localStorage.setItem(USER_KEY, JSON.stringify(data))
|
|
return data
|
|
},
|
|
|
|
async changePassword({ currentPassword, newPassword }) {
|
|
const { data } = await api.post('/v1/auth/me/password/', {
|
|
current_password: currentPassword,
|
|
new_password: newPassword,
|
|
})
|
|
return data
|
|
},
|
|
|
|
logout() {
|
|
this.accessToken = ''
|
|
this.refreshToken = ''
|
|
this.user = null
|
|
clearSessionStorage()
|
|
},
|
|
},
|
|
})
|
|
|
|
export function getStoredAccessToken() {
|
|
return localStorage.getItem(TOKEN_KEY) || ''
|
|
}
|
|
|
|
export function getStoredRefreshToken() {
|
|
return localStorage.getItem(REFRESH_KEY) || ''
|
|
}
|
|
|
|
export function setStoredAccessToken(token) {
|
|
if (token) {
|
|
localStorage.setItem(TOKEN_KEY, token)
|
|
} else {
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
}
|
|
}
|
|
|
|
export function clearAuthStorage() {
|
|
clearSessionStorage()
|
|
}
|