track requesters for builds releases deployments and migrations
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import { createHash, randomBytes, scryptSync } from 'node:crypto';
|
||||
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { AuthPrismaService } from '../auth-prisma/auth-prisma.service';
|
||||
import { GiteaService } from '../gitea/gitea.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { getUserAvatarUrl } from '../users/users.service';
|
||||
import { AdminRole } from './admin-role';
|
||||
import {
|
||||
CreateAdminSshKeyDto,
|
||||
@@ -17,6 +20,8 @@ import {
|
||||
@Injectable()
|
||||
export class AdminsService {
|
||||
constructor(
|
||||
private readonly authPrisma: AuthPrismaService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly giteaService: GiteaService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
@@ -38,8 +43,8 @@ export class AdminsService {
|
||||
});
|
||||
}
|
||||
|
||||
list() {
|
||||
return this.prisma.admin.findMany({
|
||||
async list() {
|
||||
const admins = await this.prisma.admin.findMany({
|
||||
orderBy: [{ role: 'asc' }, { createdAt: 'desc' }],
|
||||
select: {
|
||||
id: true,
|
||||
@@ -69,15 +74,46 @@ export class AdminsService {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const users = await this.authPrisma.user.findMany({
|
||||
where: { email: { in: admins.map((admin) => admin.email) } },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
profileImageMime: true,
|
||||
},
|
||||
});
|
||||
const userByEmail = new Map(users.map((user) => [user.email, user]));
|
||||
const authBaseUrl = this.configService.get<string>('AUTH_PUBLIC_URL')
|
||||
?? this.configService.get<string>('ATNMY_AUTH_URL')
|
||||
?? this.configService.get<string>('AUTH_EXTERNAL_URL')
|
||||
?? 'https://auth.autonomystudio.net';
|
||||
|
||||
return admins.map((admin) => {
|
||||
const user = userByEmail.get(admin.email);
|
||||
return {
|
||||
...admin,
|
||||
avatarUrl: getUserAvatarUrl(user?.id ?? '', user?.profileImageMime, authBaseUrl),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async create(body: CreateAdminDto) {
|
||||
await this.ensureRoleEnabled(body.role);
|
||||
const email = body.email.trim().toLowerCase();
|
||||
const name = body.name.trim();
|
||||
|
||||
await this.giteaService.createUser({
|
||||
username: createGiteaUsername(email),
|
||||
email,
|
||||
password: body.temporaryPassword,
|
||||
fullName: name,
|
||||
});
|
||||
|
||||
const admin = await this.prisma.admin.create({
|
||||
data: {
|
||||
email: body.email.trim().toLowerCase(),
|
||||
name: body.name.trim(),
|
||||
email,
|
||||
name,
|
||||
role: body.role,
|
||||
passwordHash: hashPassword(body.temporaryPassword),
|
||||
},
|
||||
@@ -186,20 +222,18 @@ export class AdminsService {
|
||||
}
|
||||
|
||||
async createSshKey(adminId: string, body: CreateAdminSshKeyDto) {
|
||||
await this.ensureExists(adminId);
|
||||
const admin = await this.ensureExists(adminId);
|
||||
|
||||
const parsedKey = parseSshPublicKey(body.publicKey);
|
||||
const label = body.label?.trim() || `${parsedKey.keyType} ${parsedKey.fingerprint}`;
|
||||
const giteaUsername = body.giteaUsername?.trim() || null;
|
||||
const giteaUsername = createGiteaUsername(admin.email);
|
||||
let giteaKeyId: number | null = null;
|
||||
|
||||
if (giteaUsername) {
|
||||
const giteaKey = await this.giteaService.createUserSshKey(giteaUsername, {
|
||||
title: label,
|
||||
key: parsedKey.normalizedKey,
|
||||
});
|
||||
giteaKeyId = giteaKey.id;
|
||||
}
|
||||
const giteaKey = await this.giteaService.createUserSshKey(giteaUsername, {
|
||||
title: label,
|
||||
key: parsedKey.normalizedKey,
|
||||
});
|
||||
giteaKeyId = giteaKey.id;
|
||||
|
||||
const sshKey = await this.prisma.adminSshKey.create({
|
||||
data: {
|
||||
@@ -273,12 +307,14 @@ export class AdminsService {
|
||||
private async ensureExists(id: string) {
|
||||
const admin = await this.prisma.admin.findUnique({
|
||||
where: { id },
|
||||
select: { id: true },
|
||||
select: { id: true, email: true },
|
||||
});
|
||||
|
||||
if (!admin) {
|
||||
throw new NotFoundException('관리자를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return admin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +345,18 @@ function hashPassword(password: string) {
|
||||
return `scrypt:${salt}:${hash}`;
|
||||
}
|
||||
|
||||
function createGiteaUsername(email: string) {
|
||||
const account = email.split('@')[0] || email;
|
||||
const username = account
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9._-]+/g, '-')
|
||||
.replace(/^[._-]+|[._-]+$/g, '')
|
||||
.slice(0, 40);
|
||||
|
||||
return username || `admin-${createHash('sha1').update(email).digest('hex').slice(0, 8)}`;
|
||||
}
|
||||
|
||||
function parseSshPublicKey(publicKey: string) {
|
||||
const normalizedKey = publicKey.trim().replace(/\s+/g, ' ');
|
||||
const [keyType, keyBody] = normalizedKey.split(' ');
|
||||
|
||||
Reference in New Issue
Block a user