feat: add invite code system for closed alpha registration
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
import { t, i18n } from '../i18n.js'
|
||||
import { auth } from '../services/auth.js'
|
||||
import { POW_SERVER } from '../services/pow-captcha.js'
|
||||
import './pow-captcha.js'
|
||||
|
||||
class AuthModal extends HTMLElement {
|
||||
@@ -14,14 +15,30 @@ class AuthModal extends HTMLElement {
|
||||
this.error = null
|
||||
this.loading = false
|
||||
this.loginAttempts = 0
|
||||
this.requireInviteCode = null
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.checkInviteCodeRequired()
|
||||
this.render()
|
||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
||||
this.boundHandleKeydown = this.handleKeydown.bind(this)
|
||||
}
|
||||
|
||||
async checkInviteCodeRequired() {
|
||||
try {
|
||||
const res = await fetch(`${POW_SERVER}/invite/validate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code: '' })
|
||||
})
|
||||
const data = await res.json()
|
||||
this.requireInviteCode = !data.valid
|
||||
} catch {
|
||||
this.requireInviteCode = false
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.unsubscribe) this.unsubscribe()
|
||||
document.removeEventListener('keydown', this.boundHandleKeydown)
|
||||
@@ -141,6 +158,22 @@ class AuthModal extends HTMLElement {
|
||||
|
||||
${this.error ? `<div class="auth-error">${this.error}</div>` : ''}
|
||||
|
||||
${this.requireInviteCode ? `
|
||||
<div class="form-group">
|
||||
<label class="label" for="invite-code">${t('auth.inviteCode')}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
id="invite-code"
|
||||
name="invite-code"
|
||||
placeholder="${t('auth.inviteCodePlaceholder')}"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
<p class="field-hint">${t('auth.inviteCodeHint')}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="form-group">
|
||||
<pow-captcha id="register-captcha"></pow-captcha>
|
||||
</div>
|
||||
@@ -274,6 +307,39 @@ class AuthModal extends HTMLElement {
|
||||
}
|
||||
|
||||
async handleRegister() {
|
||||
// Validate invite code if required
|
||||
if (this.requireInviteCode) {
|
||||
const inviteInput = this.querySelector('#invite-code')
|
||||
const code = inviteInput?.value.trim()
|
||||
if (!code) {
|
||||
this.error = t('auth.inviteCodeRequired')
|
||||
this.render()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${POW_SERVER}/invite/validate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code })
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!data.valid) {
|
||||
const errorMap = {
|
||||
'code_redeemed': t('auth.inviteCodeRedeemed'),
|
||||
'code_expired': t('auth.inviteCodeExpired')
|
||||
}
|
||||
this.error = errorMap[data.error] || t('auth.inviteCodeInvalid')
|
||||
this.render()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
this.error = t('auth.inviteCodeInvalid')
|
||||
this.render()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Require captcha for registration
|
||||
const captcha = this.querySelector('#register-captcha')
|
||||
if (!captcha?.isSolved()) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Client must find nonce where SHA256(challenge + nonce) has N leading zeros
|
||||
// Server-first: tries /pow/challenge endpoint, falls back to local generation
|
||||
|
||||
const POW_SERVER = 'https://pow.kashilo.com'
|
||||
export const POW_SERVER = 'https://pow.kashilo.com'
|
||||
const DIFFICULTY = 4
|
||||
const SERVER_TIMEOUT_MS = 1500
|
||||
|
||||
|
||||
Reference in New Issue
Block a user