Merge branch 'pr-117' into dev

This commit is contained in:
Maurice
2026-03-30 20:07:12 +02:00
2 changed files with 24 additions and 5 deletions

View File

@@ -496,7 +496,7 @@ export default function LoginPage(): React.ReactElement {
{error}
</div>
)}
<a href="/api/auth/oidc/login"
<a href={`/api/auth/oidc/login${inviteToken ? '?invite=' + encodeURIComponent(inviteToken) : ''}`}
style={{
width: '100%', padding: '12px',
background: '#111827', color: 'white',
@@ -657,7 +657,7 @@ export default function LoginPage(): React.ReactElement {
<span style={{ fontSize: 12, color: '#9ca3af' }}>{t('common.or')}</span>
<div style={{ flex: 1, height: 1, background: '#e5e7eb' }} />
</div>
<a href="/api/auth/oidc/login"
<a href={`/api/auth/oidc/login${inviteToken ? '?invite=' + encodeURIComponent(inviteToken) : ''}`}
style={{
marginTop: 12, width: '100%', padding: '12px',
background: 'white', color: '#374151',

View File

@@ -44,7 +44,7 @@ setInterval(() => {
}
}, AUTH_CODE_CLEANUP);
const pendingStates = new Map<string, { createdAt: number; redirectUri: string }>();
const pendingStates = new Map<string, { createdAt: number; redirectUri: string; inviteToken?: string }>();
setInterval(() => {
const now = Date.now();
@@ -124,8 +124,9 @@ router.get('/login', async (req: Request, res: Response) => {
const proto = (req.headers['x-forwarded-proto'] as string) || req.protocol;
const host = (req.headers['x-forwarded-host'] as string) || req.headers.host;
const redirectUri = `${proto}://${host}/api/auth/oidc/callback`;
const inviteToken = req.query.invite as string | undefined;
pendingStates.set(state, { createdAt: Date.now(), redirectUri });
pendingStates.set(state, { createdAt: Date.now(), redirectUri, inviteToken });
const params = new URLSearchParams({
response_type: 'code',
@@ -222,7 +223,16 @@ router.get('/callback', async (req: Request, res: Response) => {
const userCount = (db.prepare('SELECT COUNT(*) as count FROM users').get() as { count: number }).count;
const isFirstUser = userCount === 0;
if (!isFirstUser) {
let validInvite: any = null;
if (pending.inviteToken) {
validInvite = db.prepare('SELECT * FROM invite_tokens WHERE token = ?').get(pending.inviteToken);
if (validInvite) {
if (validInvite.max_uses > 0 && validInvite.used_count >= validInvite.max_uses) validInvite = null;
if (validInvite?.expires_at && new Date(validInvite.expires_at) < new Date()) validInvite = null;
}
}
if (!isFirstUser && !validInvite) {
const setting = db.prepare("SELECT value FROM app_settings WHERE key = 'allow_registration'").get() as { value: string } | undefined;
if (setting?.value === 'false') {
return res.redirect(frontendUrl('/login?oidc_error=registration_disabled'));
@@ -242,6 +252,15 @@ router.get('/callback', async (req: Request, res: Response) => {
'INSERT INTO users (username, email, password_hash, role, oidc_sub, oidc_issuer) VALUES (?, ?, ?, ?, ?, ?)'
).run(username, email, hash, role, sub, config.issuer);
if (validInvite) {
const updated = db.prepare(
'UPDATE invite_tokens SET used_count = used_count + 1 WHERE id = ? AND (max_uses = 0 OR used_count < max_uses)'
).run(validInvite.id);
if (updated.changes === 0) {
console.warn(`[OIDC] Invite token ${pending.inviteToken?.slice(0, 8)}... exceeded max_uses (race condition)`);
}
}
user = { id: Number(result.lastInsertRowid), username, email, role } as User;
}