feat(sync): sync all FDISK members, auto-creating dashboard accounts for users not yet logged in
This commit is contained in:
@@ -100,7 +100,22 @@ class AuthController {
|
||||
|
||||
// Step 4: Find or create user in database
|
||||
let user = await userService.findByAuthentikSub(userInfo.sub);
|
||||
const isNewUser = !user;
|
||||
let isNewUser = !user;
|
||||
|
||||
// Check for a FDISK-pre-created account to claim on first Authentik login
|
||||
if (!user) {
|
||||
const { given_name: fdiskGivenName, family_name: fdiskFamilyName } = extractNames(userInfo);
|
||||
if (fdiskGivenName && fdiskFamilyName) {
|
||||
const fdiskUser = await userService.findFdiskUserByName(fdiskGivenName, fdiskFamilyName);
|
||||
if (fdiskUser) {
|
||||
user = await userService.claimFdiskUser(fdiskUser.id, userInfo.sub, userInfo.email);
|
||||
if (user) {
|
||||
isNewUser = false;
|
||||
logger.info('Claimed FDISK-pre-created user on first login', { userId: fdiskUser.id, sub: userInfo.sub });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
// User doesn't exist, create new user
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS sync_source VARCHAR(16) DEFAULT NULL;
|
||||
@@ -17,6 +17,7 @@ export interface User {
|
||||
updated_at: Date;
|
||||
preferences?: any; // JSONB
|
||||
authentik_groups: string[];
|
||||
sync_source?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateUserData {
|
||||
|
||||
@@ -413,6 +413,53 @@ class UserService {
|
||||
throw new Error('Failed to update user groups');
|
||||
}
|
||||
}
|
||||
|
||||
async findFdiskUserByName(givenName: string, familyName: string): Promise<User | null> {
|
||||
try {
|
||||
const result = await pool.query<User>(
|
||||
`SELECT id, email, authentik_sub, name, preferred_username, given_name,
|
||||
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
|
||||
is_active, last_login_at, created_at, updated_at, preferences,
|
||||
authentik_groups, sync_source
|
||||
FROM users
|
||||
WHERE sync_source = 'fdisk'
|
||||
AND last_login_at IS NULL
|
||||
AND LOWER(given_name) = LOWER($1)
|
||||
AND LOWER(family_name) = LOWER($2)`,
|
||||
[givenName, familyName]
|
||||
);
|
||||
if (result.rows.length === 1) return result.rows[0];
|
||||
if (result.rows.length > 1) {
|
||||
logger.warn('Ambiguous FDISK name match on login — skipping merge', { givenName, familyName, count: result.rows.length });
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.error('Error finding FDISK user by name', { error });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async claimFdiskUser(userId: string, authentikSub: string, email: string): Promise<User | null> {
|
||||
try {
|
||||
const result = await pool.query<User>(
|
||||
`UPDATE users SET
|
||||
authentik_sub = $2,
|
||||
email = $3
|
||||
WHERE id = $1 AND sync_source = 'fdisk' AND last_login_at IS NULL
|
||||
RETURNING id, email, authentik_sub, name, preferred_username, given_name,
|
||||
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
|
||||
is_active, last_login_at, created_at, updated_at, preferences,
|
||||
authentik_groups, sync_source`,
|
||||
[userId, authentikSub, email]
|
||||
);
|
||||
if (result.rows.length === 0) return null;
|
||||
logger.info('FDISK user claimed by Authentik login', { userId, authentikSub });
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('Error claiming FDISK user', { error, userId });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserService();
|
||||
|
||||
Reference in New Issue
Block a user