feat(auth): integrate normalizePermissionKeys for consistent permission handling
- Updated auth, admin, and auth-guards routes to utilize normalizePermissionKeys for permissions. - Enhanced permission mapping to ensure legacy keys are correctly transformed to current catalog keys. - Improved overall permission validation and user access management across the application.
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
|||||||
type UserRow,
|
type UserRow,
|
||||||
} from '@authportal/db'
|
} from '@authportal/db'
|
||||||
import type { AppId, MeResponse } from '@authportal/shared'
|
import type { AppId, MeResponse } from '@authportal/shared'
|
||||||
import { APP_IDS } from '@authportal/shared'
|
import { APP_IDS, normalizePermissionKeys } from '@authportal/shared'
|
||||||
|
|
||||||
export type AuthUser = {
|
export type AuthUser = {
|
||||||
id: string
|
id: string
|
||||||
@@ -68,7 +68,9 @@ export function loadAuthUser(
|
|||||||
user: UserRow,
|
user: UserRow,
|
||||||
): AuthUser {
|
): AuthUser {
|
||||||
const apps = getUserApps(request.server.db, user.id)
|
const apps = getUserApps(request.server.db, user.id)
|
||||||
const permissions = getUserPermissions(request.server.db, user.id)
|
const permissions = normalizePermissionKeys(
|
||||||
|
getUserPermissions(request.server.db, user.id),
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
allPermissionKeys,
|
allPermissionKeys,
|
||||||
appSwitcherConfigSchema,
|
appSwitcherConfigSchema,
|
||||||
createUserRequestSchema,
|
createUserRequestSchema,
|
||||||
|
normalizePermissionKeys,
|
||||||
patchUserRequestSchema,
|
patchUserRequestSchema,
|
||||||
putUserAccessRequestSchema,
|
putUserAccessRequestSchema,
|
||||||
type AdminUser,
|
type AdminUser,
|
||||||
@@ -34,7 +35,7 @@ function mapUser(
|
|||||||
const apps = getUserApps(db, user.id).filter((a): a is AppId =>
|
const apps = getUserApps(db, user.id).filter((a): a is AppId =>
|
||||||
(APP_IDS as readonly string[]).includes(a),
|
(APP_IDS as readonly string[]).includes(a),
|
||||||
)
|
)
|
||||||
const permissions = getUserPermissions(db, user.id)
|
const permissions = normalizePermissionKeys(getUserPermissions(db, user.id))
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
@@ -93,12 +94,13 @@ export async function adminRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const data = parsed.data
|
const data = parsed.data
|
||||||
|
const permissions = normalizePermissionKeys(data.permissions)
|
||||||
if (getUserByEmail(app.db, data.email)) {
|
if (getUserByEmail(app.db, data.email)) {
|
||||||
return reply.status(409).send({
|
return reply.status(409).send({
|
||||||
error: { code: 'CONFLICT', message: 'Email уже занят' },
|
error: { code: 'CONFLICT', message: 'Email уже занят' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const accessError = validateAccess(data.apps, data.permissions)
|
const accessError = validateAccess(data.apps, permissions)
|
||||||
if (accessError) {
|
if (accessError) {
|
||||||
return reply.status(400).send({
|
return reply.status(400).send({
|
||||||
error: { code: 'VALIDATION_ERROR', message: accessError },
|
error: { code: 'VALIDATION_ERROR', message: accessError },
|
||||||
@@ -112,7 +114,7 @@ export async function adminRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
passwordHash,
|
passwordHash,
|
||||||
isAdmin: data.is_admin,
|
isAdmin: data.is_admin,
|
||||||
})
|
})
|
||||||
setUserAccess(app.db, user.id, data.apps, data.permissions)
|
setUserAccess(app.db, user.id, data.apps, permissions)
|
||||||
return reply.status(201).send(mapUser(app.db, getUserById(app.db, user.id)!))
|
return reply.status(201).send(mapUser(app.db, getUserById(app.db, user.id)!))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -190,10 +192,8 @@ export async function adminRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
error: { code: 'NOT_FOUND', message: 'Пользователь не найден' },
|
error: { code: 'NOT_FOUND', message: 'Пользователь не найден' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const accessError = validateAccess(
|
const permissions = normalizePermissionKeys(parsed.data.permissions)
|
||||||
parsed.data.apps,
|
const accessError = validateAccess(parsed.data.apps, permissions)
|
||||||
parsed.data.permissions,
|
|
||||||
)
|
|
||||||
if (accessError) {
|
if (accessError) {
|
||||||
return reply.status(400).send({
|
return reply.status(400).send({
|
||||||
error: { code: 'VALIDATION_ERROR', message: accessError },
|
error: { code: 'VALIDATION_ERROR', message: accessError },
|
||||||
@@ -203,7 +203,7 @@ export async function adminRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
app.db,
|
app.db,
|
||||||
request.params.id,
|
request.params.id,
|
||||||
parsed.data.apps,
|
parsed.data.apps,
|
||||||
parsed.data.permissions,
|
permissions,
|
||||||
)
|
)
|
||||||
return mapUser(app.db, getUserById(app.db, request.params.id)!)
|
return mapUser(app.db, getUserById(app.db, request.params.id)!)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
PERMISSION_CATALOG,
|
PERMISSION_CATALOG,
|
||||||
appsMetaFromSwitcher,
|
appsMetaFromSwitcher,
|
||||||
loginRequestSchema,
|
loginRequestSchema,
|
||||||
|
normalizePermissionKeys,
|
||||||
type LoginResponse,
|
type LoginResponse,
|
||||||
} from '@authportal/shared'
|
} from '@authportal/shared'
|
||||||
import {
|
import {
|
||||||
@@ -52,7 +53,9 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apps = getUserApps(app.db, user.id)
|
const apps = getUserApps(app.db, user.id)
|
||||||
const permissions = getUserPermissions(app.db, user.id)
|
const permissions = normalizePermissionKeys(
|
||||||
|
getUserPermissions(app.db, user.id),
|
||||||
|
)
|
||||||
const me = toMe(user, apps, permissions)
|
const me = toMe(user, apps, permissions)
|
||||||
|
|
||||||
const expiresAt = new Date(
|
const expiresAt = new Date(
|
||||||
|
|||||||
@@ -244,6 +244,32 @@ export function allPermissionKeys(): string[] {
|
|||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Old bgp section ids → current catalog (peers/apply/settings renamed). */
|
||||||
|
const LEGACY_PERMISSION_MAP: Record<string, string> = {
|
||||||
|
'bgp:peers:read': 'bgp:network:read',
|
||||||
|
'bgp:peers:write': 'bgp:network:write',
|
||||||
|
'bgp:apply:write': 'bgp:operations:admin',
|
||||||
|
'bgp:settings:admin': 'bgp:tenant_settings:admin',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map legacy keys and drop anything not in the current catalog.
|
||||||
|
* Safe for JWT issue and admin save after catalog changes.
|
||||||
|
*/
|
||||||
|
export function normalizePermissionKeys(
|
||||||
|
permissions: readonly string[],
|
||||||
|
): string[] {
|
||||||
|
const allowed = new Set(allPermissionKeys())
|
||||||
|
const out = new Set<string>()
|
||||||
|
for (const raw of permissions) {
|
||||||
|
const mapped = LEGACY_PERMISSION_MAP[raw] ?? raw
|
||||||
|
if (allowed.has(mapped)) {
|
||||||
|
out.add(mapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...out].sort()
|
||||||
|
}
|
||||||
|
|
||||||
export const permissionKeySchema = z
|
export const permissionKeySchema = z
|
||||||
.string()
|
.string()
|
||||||
.regex(/^[a-z]+:[a-z0-9_]+:(read|write|admin)$/)
|
.regex(/^[a-z]+:[a-z0-9_]+:(read|write|admin)$/)
|
||||||
|
|||||||
Reference in New Issue
Block a user