This commit is contained in:
2026-08-03 13:47:40 +09:00
parent 5ff5df91a4
commit 4c8a9998b1
5 changed files with 74 additions and 4 deletions
@@ -0,0 +1,5 @@
ALTER TABLE "device_authorizations"
ADD COLUMN "registration_reference" TEXT;
CREATE INDEX "device_authorizations_registration_reference_idx"
ON "device_authorizations"("registration_reference");
+2
View File
@@ -237,6 +237,7 @@ model DeviceAuthorization {
authUserName String? @map("auth_user_name") authUserName String? @map("auth_user_name")
authUserRole String? @map("auth_user_role") authUserRole String? @map("auth_user_role")
authUserRoles Json? @map("auth_user_roles") authUserRoles Json? @map("auth_user_roles")
registrationReference String? @map("registration_reference")
device Json? device Json?
expiresAt DateTime @map("expires_at") @db.Timestamptz(3) expiresAt DateTime @map("expires_at") @db.Timestamptz(3)
approvedAt DateTime? @map("approved_at") @db.Timestamptz(3) approvedAt DateTime? @map("approved_at") @db.Timestamptz(3)
@@ -245,6 +246,7 @@ model DeviceAuthorization {
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([status, expiresAt]) @@index([status, expiresAt])
@@index([registrationReference])
@@map("device_authorizations") @@map("device_authorizations")
} }
@@ -4,6 +4,10 @@ import { IsNotEmpty, IsObject, IsOptional, IsString, ValidateNested } from 'clas
import { ProvisioningDeviceDto } from './create-provisioning.dto'; import { ProvisioningDeviceDto } from './create-provisioning.dto';
export class CreateDeviceAuthorizationDto { export class CreateDeviceAuthorizationDto {
@IsString()
@IsOptional()
registrationReference?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
hostname?: string; hostname?: string;
+15 -1
View File
@@ -1,10 +1,11 @@
import { Body, Controller, Get, Headers, Post, UnauthorizedException } from '@nestjs/common'; import { Body, Controller, Get, Headers, Param, Post, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { CreateProvisioningDto } from './dto/create-provisioning.dto'; import { CreateProvisioningDto } from './dto/create-provisioning.dto';
import { CreateSessionProvisioningDto } from './dto/create-session-provisioning.dto'; import { CreateSessionProvisioningDto } from './dto/create-session-provisioning.dto';
import { import {
ConsumeDeviceAuthorizationDto, ConsumeDeviceAuthorizationDto,
CreateDeviceAuthorizationDto,
} from './dto/device-authorization.dto'; } from './dto/device-authorization.dto';
import { ProvisioningService } from './provisioning.service'; import { ProvisioningService } from './provisioning.service';
import type { ProvisioningResponse } from './provisioning.service'; import type { ProvisioningResponse } from './provisioning.service';
@@ -37,9 +38,11 @@ export class ProvisioningController {
@Post('device-authorizations') @Post('device-authorizations')
createDeviceAuthorization( createDeviceAuthorization(
@Headers('authorization') authorization: string | undefined, @Headers('authorization') authorization: string | undefined,
@Body() body: CreateDeviceAuthorizationDto,
) { ) {
return this.provisioningService.createDeviceAuthorization( return this.provisioningService.createDeviceAuthorization(
this.getBearerToken(authorization), this.getBearerToken(authorization),
body,
); );
} }
@@ -66,6 +69,17 @@ export class ProvisioningController {
return this.provisioningService.consumeDeviceAuthorization(body); return this.provisioningService.consumeDeviceAuthorization(body);
} }
@Get('device-authorizations/registration/:reference')
getDeviceAuthorizationByRegistrationReference(
@Headers('authorization') authorization: string | undefined,
@Param('reference') reference: string,
) {
return this.provisioningService.getDeviceAuthorizationByRegistrationReference(
this.getBearerToken(authorization),
reference,
);
}
@Post('ssh-keys/lookup') @Post('ssh-keys/lookup')
listSshKeys( listSshKeys(
@Headers('x-lms-gateway-token') token: string | undefined, @Headers('x-lms-gateway-token') token: string | undefined,
+48 -3
View File
@@ -14,7 +14,10 @@ import { createHash, randomBytes } from 'crypto';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { CreateProvisioningDto } from './dto/create-provisioning.dto'; import { CreateProvisioningDto } from './dto/create-provisioning.dto';
import { CreateSessionProvisioningDto } from './dto/create-session-provisioning.dto'; import { CreateSessionProvisioningDto } from './dto/create-session-provisioning.dto';
import { ConsumeDeviceAuthorizationDto } from './dto/device-authorization.dto'; import {
ConsumeDeviceAuthorizationDto,
CreateDeviceAuthorizationDto,
} from './dto/device-authorization.dto';
export type ProvisioningResponse = { export type ProvisioningResponse = {
installTailscale: boolean; installTailscale: boolean;
@@ -79,7 +82,10 @@ export class ProvisioningService {
return this.createProvisioningForUser(provisioningDto, authUser); return this.createProvisioningForUser(provisioningDto, authUser);
} }
async createDeviceAuthorization(accessToken: string | undefined) { async createDeviceAuthorization(
accessToken: string | undefined,
input: CreateDeviceAuthorizationDto = {},
) {
const authUser = await this.authenticateAccessToken(accessToken); const authUser = await this.authenticateAccessToken(accessToken);
const expiresInSeconds = Math.max( const expiresInSeconds = Math.max(
60, 60,
@@ -100,6 +106,7 @@ export class ProvisioningService {
authUserName: authUser.name ?? null, authUserName: authUser.name ?? null,
authUserRole: authUser.role ?? null, authUserRole: authUser.role ?? null,
authUserRoles: authUser.roles ? this.cleanJson(authUser.roles) : undefined, authUserRoles: authUser.roles ? this.cleanJson(authUser.roles) : undefined,
registrationReference: this.cleanString(input.registrationReference),
expiresAt, expiresAt,
approvedAt: new Date(), approvedAt: new Date(),
}, },
@@ -155,6 +162,35 @@ export class ProvisioningService {
return { cancelled: result.count > 0 }; return { cancelled: result.count > 0 };
} }
async getDeviceAuthorizationByRegistrationReference(
accessToken: string | undefined,
reference: string,
) {
const authUser = await this.authenticateAccessToken(accessToken);
const registrationReference = this.cleanString(reference);
if (!registrationReference) {
throw new NotFoundException('Device authorization request was not found');
}
const authorization = await this.prisma.deviceAuthorization.findFirst({
where: {
registrationReference,
authUserId: authUser.id,
accountEmail: authUser.email,
},
orderBy: { createdAt: 'desc' },
select: {
status: true,
expiresAt: true,
consumedAt: true,
accountEmail: true,
device: true,
},
});
return authorization ?? null;
}
async consumeDeviceAuthorization(body: ConsumeDeviceAuthorizationDto) { async consumeDeviceAuthorization(body: ConsumeDeviceAuthorizationDto) {
const userCode = this.normalizeUserCode(body.userCode); const userCode = this.normalizeUserCode(body.userCode);
const authorization = await this.prisma.deviceAuthorization.findUnique({ const authorization = await this.prisma.deviceAuthorization.findUnique({
@@ -202,7 +238,16 @@ export class ProvisioningService {
const provisioning = await this.createProvisioningForUser(body, authUser); const provisioning = await this.createProvisioningForUser(body, authUser);
await this.prisma.deviceAuthorization.update({ await this.prisma.deviceAuthorization.update({
where: { id: authorization.id }, where: { id: authorization.id },
data: { status: 'CONSUMED', consumedAt: new Date() }, data: {
status: 'CONSUMED',
consumedAt: new Date(),
device: this.cleanJson({
...(body.device ?? {}),
hostname: body.hostname ?? body.device?.hostname ?? null,
sshUser: body.sshUser ?? body.device?.sshUser ?? null,
workspaceRoot: body.workspaceRoot ?? body.device?.workspaceRoot ?? null,
}),
},
}); });
return provisioning; return provisioning;
} catch (error) { } catch (error) {