fix: License key format, login populates license, case-insensitive email
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

- admin.service.ts: createLicense() now uses CORR-XXXX-XXXX-XXXX format
  instead of raw hex hash
- admin.service.ts: getLicenses() flattens owner_email in response to
  match frontend expected shape
- auth.service.ts: Login/register responses now include full license
  object so frontend can populate auth store
- auth.service.ts: Email lookups are case-insensitive (LOWER()) to
  prevent duplicate accounts from case variations
- LoginView/RegisterView: Call setLicense() after setAuth()
- AdminLicenses: Handle null expires_at (was showing Dec 31, 1969),
  fix nullable types, fix query param name (per_page → limit)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-21 15:32:35 -05:00
parent 14b099b075
commit 8253680fbd
6 changed files with 77 additions and 28 deletions

View File

@@ -39,6 +39,9 @@ async function handleLogin() {
}
authStore.setAuth(response)
if (response.license) {
authStore.setLicense(response.license)
}
router.push('/')
} catch (err: unknown) {
if (err instanceof Error) {
@@ -68,6 +71,9 @@ async function handleTotpVerify() {
})
authStore.setAuth(response)
if (response.license) {
authStore.setLicense(response.license)
}
router.push('/')
} catch (err: unknown) {
totpCode.value = ''

View File

@@ -57,6 +57,9 @@ async function handleRegister() {
})
authStore.setAuth(response)
if (response.license) {
authStore.setLicense(response.license)
}
router.push('/setup')
} catch (err: unknown) {
if (err instanceof Error) {

View File

@@ -9,20 +9,20 @@ interface License {
id: string
license_key: string
owner_email: string
server_name: string
server_name: string | null
status: 'active' | 'suspended' | 'expired' | 'revoked'
created_at: string
expires_at: string
expires_at: string | null
}
interface LicenseDetail {
id: string
license_key: string
owner_email: string
server_name: string
server_name: string | null
status: string
created_at: string
expires_at: string
expires_at: string | null
team_count: number
wipe_count: number
server_connection: {
@@ -67,8 +67,11 @@ const statusBadgeClass: Record<string, string> = {
revoked: 'bg-red-500/10 text-red-400',
}
function formatDate(iso: string): string {
return new Date(iso).toLocaleDateString('en-US', {
function formatDate(iso: string | null | undefined): string {
if (!iso) return '—'
const d = new Date(iso)
if (isNaN(d.getTime()) || d.getTime() === 0) return '—'
return d.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
@@ -80,7 +83,7 @@ async function fetchLicenses() {
try {
const params = new URLSearchParams({
page: page.value.toString(),
per_page: perPage.toString(),
limit: perPage.toString(),
})
if (searchQuery.value.trim()) params.set('search', searchQuery.value.trim())
if (statusFilter.value !== 'all') params.set('status', statusFilter.value)