This commit is contained in:
@@ -3,6 +3,7 @@ import { randomInt } from 'crypto'
|
||||
import { z } from 'zod'
|
||||
import { config } from '../config'
|
||||
import { sendEmailVerificationCode } from '../mail'
|
||||
import { prisma } from '../prisma'
|
||||
import { verifyPassword } from '../security/password'
|
||||
import { signAccessToken, verifyAccessToken } from '../security/tokens'
|
||||
import type { AuthUser } from '../types'
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
findOrCreateOAuthUser,
|
||||
findUserProfileImage,
|
||||
findUserByEmail,
|
||||
findUserById,
|
||||
listOrganizations,
|
||||
isLoginBlocked,
|
||||
markLastLogin,
|
||||
@@ -153,6 +155,14 @@ const userResponse = (user: AuthUser) => ({
|
||||
|
||||
const requireAdmin = (user: AuthUser): boolean => user.role === 'ADMIN'
|
||||
|
||||
const canManageLmsStudents = (user: AuthUser): boolean => (
|
||||
user.role === 'ADMIN'
|
||||
|| (user.appRoles ?? []).some((appRole) => (
|
||||
appRole.appKey.toUpperCase() === 'LMS'
|
||||
&& ['INSTRUCTOR', 'LMS_ADMIN'].includes(appRole.roleCode.toUpperCase())
|
||||
))
|
||||
)
|
||||
|
||||
const accessTokenResponse = async (user: AuthUser) => ({
|
||||
accessToken: await signAccessToken(user),
|
||||
accessTokenExpiresIn: config.accessTokenTtlSeconds,
|
||||
@@ -802,6 +812,49 @@ authRoutes.post('/admin/users', authRequired, async (c) => {
|
||||
}
|
||||
})
|
||||
|
||||
authRoutes.post('/instructor/students', authRequired, async (c) => {
|
||||
const instructor = c.get('user')
|
||||
if (!canManageLmsStudents(instructor)) return c.json({ error: 'forbidden' }, 403)
|
||||
|
||||
const body = adminCreateUserSchema.safeParse(await c.req.json().catch(() => null))
|
||||
if (!body.success) return c.json({ error: 'invalid_request', details: body.error.flatten() }, 400)
|
||||
|
||||
const organizationId = instructor.primaryAffiliation?.organizationId
|
||||
if (instructor.role !== 'ADMIN' && (!organizationId || body.data.organizationId !== organizationId)) {
|
||||
return c.json({ error: 'forbidden' }, 403)
|
||||
}
|
||||
|
||||
const email = body.data.email.toLowerCase()
|
||||
const existingUser = await findUserByEmail(email)
|
||||
if (existingUser) return c.json({ error: 'email_already_registered' }, 409)
|
||||
|
||||
try {
|
||||
const user = await createManagedUser({
|
||||
email,
|
||||
password: body.data.password,
|
||||
name: body.data.name,
|
||||
organizationId: body.data.organizationId,
|
||||
department: body.data.department,
|
||||
position: body.data.position,
|
||||
licenseCode: body.data.licenseCode,
|
||||
})
|
||||
|
||||
await prisma.userAppRole.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
appKey: 'LMS',
|
||||
roleCode: 'STUDENT',
|
||||
assignedById: instructor.id,
|
||||
},
|
||||
})
|
||||
|
||||
const createdUser = await findUserById(user.id)
|
||||
return c.json({ user: userResponse(createdUser ?? user) }, 201)
|
||||
} catch {
|
||||
return c.json({ error: 'user_create_failed' }, 400)
|
||||
}
|
||||
})
|
||||
|
||||
authRoutes.post('/admin/users/:userId/affiliations', authRequired, async (c) => {
|
||||
if (!requireAdmin(c.get('user'))) return c.json({ error: 'forbidden' }, 403)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user