Add JWT auth with per-user data isolation and account settings.

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>
This commit is contained in:
2026-06-28 21:10:16 +03:00
co-authored by Cursor
parent 85b8b2e9b8
commit 4eb4c145b6
40 changed files with 2301 additions and 117 deletions
+126
View File
@@ -0,0 +1,126 @@
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()
}