feat: allow creating OAuth-only users without a password

Admin can now toggle "OAuth-only (no password)" when creating a user.
These users have no password hash and authProvider set to 'none', so
they can only log in via an OAuth/SSO provider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-03-09 11:35:12 +01:00
co-authored by Claude Opus 4.6
parent 7297dc0616
commit 4a280b5932
3 changed files with 31 additions and 18 deletions
+3 -3
View File
@@ -194,10 +194,10 @@ export function revokeAllSessions(userId: number, exceptToken?: string): void {
export async function createUser(
email: string,
displayName: string,
password: string,
password: string | null,
role: 'admin' | 'user' = 'user'
): Promise<User> {
const hash = await argon2.hash(password);
const hash = password ? await argon2.hash(password) : null;
const result = db
.insert(users)
.values({
@@ -205,7 +205,7 @@ export async function createUser(
displayName,
role,
passwordHash: hash,
authProvider: 'password',
authProvider: password ? 'password' : 'none',
createdAt: new Date()
})
.returning()
+24 -11
View File
@@ -11,17 +11,23 @@
let newDisplayName = $state('');
let newPassword = $state('');
let newRole = $state<'admin' | 'user'>('user');
let oauthOnly = $state(false);
let createMsg = $state('');
let createError = $state(false);
async function createUser() {
createMsg = '';
if (!newEmail || !newPassword) {
createMsg = 'Email and password are required';
if (!newEmail) {
createMsg = 'Email is required';
createError = true;
return;
}
if (newPassword.length < 8) {
if (!oauthOnly && !newPassword) {
createMsg = 'Password is required (or enable OAuth-only)';
createError = true;
return;
}
if (!oauthOnly && newPassword.length < 8) {
createMsg = 'Password must be at least 8 characters';
createError = true;
return;
@@ -33,7 +39,7 @@
body: JSON.stringify({
email: newEmail,
displayName: newDisplayName || newEmail.split('@')[0],
password: newPassword,
password: oauthOnly ? undefined : newPassword,
role: newRole
})
});
@@ -44,6 +50,7 @@
newDisplayName = '';
newPassword = '';
newRole = 'user';
oauthOnly = false;
createMsg = 'User created';
createError = false;
} else {
@@ -125,13 +132,19 @@
placeholder="Display name (optional)"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<input
type="password"
bind:value={newPassword}
placeholder="Password (min 8 characters)"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<PasswordStrengthMeter password={newPassword} />
<label class="flex items-center gap-2 text-sm text-[var(--text)]">
<input type="checkbox" bind:checked={oauthOnly} />
OAuth-only (no password)
</label>
{#if !oauthOnly}
<input
type="password"
bind:value={newPassword}
placeholder="Password (min 8 characters)"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<PasswordStrengthMeter password={newPassword} />
{/if}
<select
bind:value={newRole}
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none focus:border-[var(--primary)]"
+4 -4
View File
@@ -12,14 +12,14 @@ export const POST: RequestHandler = async ({ request, ...event }) => {
requireAdmin(event);
const { email, displayName, password, role } = await request.json();
if (!email || !password) {
throw error(400, 'Email and password are required');
if (!email) {
throw error(400, 'Email is required');
}
if (password.length < 8) {
if (password && password.length < 8) {
throw error(400, 'Password must be at least 8 characters');
}
const user = await createUser(email, displayName || email.split('@')[0], password, role || 'user');
const user = await createUser(email, displayName || email.split('@')[0], password || null, role || 'user');
return json(user, { status: 201 });
};