make working apikey db
fix bug in user delete call
This commit is contained in:
parent
caa18ea3bd
commit
a7981ce8ad
11
backend/src/collections/apikey-db/apikey-db.module.ts
Normal file
11
backend/src/collections/apikey-db/apikey-db.module.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { EApiKeyBackend } from '../../database/entities/apikey.entity';
|
||||
import { ApikeyDbService } from './apikey-db.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([EApiKeyBackend])],
|
||||
providers: [ApikeyDbService],
|
||||
exports: [ApikeyDbService],
|
||||
})
|
||||
export class ApikeyDbModule {}
|
108
backend/src/collections/apikey-db/apikey-db.service.ts
Normal file
108
backend/src/collections/apikey-db/apikey-db.service.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { FindResult } from 'picsur-shared/dist/types/find-result';
|
||||
import { generateRandomString } from 'picsur-shared/dist/util/random';
|
||||
import { Repository } from 'typeorm';
|
||||
import { EApiKeyBackend } from '../../database/entities/apikey.entity';
|
||||
import { EUserBackend } from '../../database/entities/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ApikeyDbService {
|
||||
constructor(
|
||||
@InjectRepository(EApiKeyBackend)
|
||||
private readonly apikeyRepo: Repository<EApiKeyBackend>,
|
||||
) {}
|
||||
|
||||
async createApiKey(userid: string): AsyncFailable<EApiKeyBackend<string>> {
|
||||
const apikey = new EApiKeyBackend<string>();
|
||||
apikey.user = userid;
|
||||
apikey.key = generateRandomString(32); // Might collide, probably not
|
||||
|
||||
/*
|
||||
And yes it might be more secure here to sha256 the key, to ensure that they are not leaked upon db breach
|
||||
But this would mean that the user has to keep track of it themselves, and it makes many other things less smooth
|
||||
So just foking protect ya database, and we'll be fine
|
||||
*/
|
||||
|
||||
try {
|
||||
return this.apikeyRepo.save(apikey);
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
async resolve(
|
||||
key: string
|
||||
): AsyncFailable<EApiKeyBackend<EUserBackend>> {
|
||||
try {
|
||||
const apikey = await this.apikeyRepo.findOne({
|
||||
where: { key },
|
||||
relations: ['user'],
|
||||
});
|
||||
if (!apikey) return Fail(FT.NotFound, 'API key not found');
|
||||
return apikey as EApiKeyBackend<EUserBackend>;
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(
|
||||
key: string,
|
||||
userid: string | undefined,
|
||||
): AsyncFailable<EApiKeyBackend<string>> {
|
||||
try {
|
||||
const apikey = await this.apikeyRepo.findOne({
|
||||
where: { user: userid, key },
|
||||
loadRelationIds: true,
|
||||
});
|
||||
if (!apikey) return Fail(FT.NotFound, 'API key not found');
|
||||
return apikey as EApiKeyBackend<string>;
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
async findMany(
|
||||
count: number,
|
||||
page: number,
|
||||
userid: string | undefined,
|
||||
): AsyncFailable<FindResult<EApiKeyBackend<string>>> {
|
||||
if (count < 1 || page < 0) return Fail(FT.UsrValidation, 'Invalid page');
|
||||
if (count > 100) return Fail(FT.UsrValidation, 'Too many results');
|
||||
|
||||
try {
|
||||
const [apikeys, amount] = await this.apikeyRepo.findAndCount({
|
||||
where: { user: userid },
|
||||
skip: count * page,
|
||||
take: count,
|
||||
loadRelationIds: true,
|
||||
});
|
||||
|
||||
return {
|
||||
results: apikeys as EApiKeyBackend<string>[],
|
||||
total: amount,
|
||||
page,
|
||||
pages: Math.ceil(amount / count),
|
||||
};
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteApiKey(
|
||||
key: string,
|
||||
userid: string | undefined,
|
||||
): AsyncFailable<EApiKeyBackend<string>> {
|
||||
const apikeyToDelete = await this.findOne(key, userid);
|
||||
if (HasFailed(apikeyToDelete)) return apikeyToDelete;
|
||||
|
||||
try {
|
||||
return (await this.apikeyRepo.remove(
|
||||
apikeyToDelete,
|
||||
)) as EApiKeyBackend<string>;
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,15 +73,15 @@ export class UserDbService {
|
|||
}
|
||||
|
||||
public async delete(uuid: string): AsyncFailable<EUserBackend> {
|
||||
const userToModify = await this.findOne(uuid);
|
||||
if (HasFailed(userToModify)) return userToModify;
|
||||
const userToDelete = await this.findOne(uuid);
|
||||
if (HasFailed(userToDelete)) return userToDelete;
|
||||
|
||||
if (UndeletableUsersList.includes(userToModify.username)) {
|
||||
if (UndeletableUsersList.includes(userToDelete.username)) {
|
||||
return Fail(FT.Permission, 'Cannot delete system user');
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.usersRepository.remove(userToModify);
|
||||
return await this.usersRepository.remove(userToDelete);
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory {
|
|||
const varOptions = this.getTypeOrmServerOptions();
|
||||
return {
|
||||
type: 'postgres' as 'postgres',
|
||||
synchronize: false,
|
||||
synchronize: !this.hostService.isProduction(),
|
||||
|
||||
migrationsRun: true,
|
||||
|
||||
|
|
|
@ -1,19 +1,33 @@
|
|||
import { EApiKey } from 'picsur-shared/dist/entities/apikey.entity';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
import { EApiKeySchema } from 'picsur-shared/dist/entities/apikey.entity';
|
||||
import { CreateDateColumn, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
import { EUserBackend } from './user.entity';
|
||||
|
||||
const OverriddenEApiKeySchema = EApiKeySchema.omit({ user: true }).merge(
|
||||
z.object({
|
||||
user: z.string().or(z.object({})),
|
||||
}),
|
||||
);
|
||||
type OverriddenEApiKey = z.infer<typeof OverriddenEApiKeySchema>;
|
||||
|
||||
@Entity()
|
||||
export class EApiKeyBackend implements EApiKey {
|
||||
export class EApiKeyBackend<
|
||||
T extends string | EUserBackend = string | EUserBackend,
|
||||
> implements OverriddenEApiKey
|
||||
{
|
||||
@PrimaryColumn({
|
||||
nullable: false
|
||||
nullable: false,
|
||||
unique: true,
|
||||
})
|
||||
key: string;
|
||||
|
||||
@Column({
|
||||
@ManyToOne(() => EUserBackend, (user) => user.apikeys, {
|
||||
nullable: false,
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
user_id: string;
|
||||
user: T;
|
||||
|
||||
@Column({
|
||||
@CreateDateColumn({
|
||||
nullable: false,
|
||||
})
|
||||
created: Date;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { EUserSchema } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
import { EApiKeyBackend } from './apikey.entity';
|
||||
|
||||
// Different data for public and private
|
||||
const OverriddenEUserSchema = EUserSchema.omit({ hashedPassword: true }).merge(
|
||||
|
@ -24,4 +25,8 @@ export class EUserBackend implements OverriddenEUser {
|
|||
|
||||
@Column({ nullable: false, select: false })
|
||||
hashed_password?: string;
|
||||
|
||||
// This will never be populated, it is only here to auto delete apikeys when a user is deleted
|
||||
@OneToMany(() => EApiKeyBackend, (apikey) => apikey.user)
|
||||
apikeys?: EApiKeyBackend[];
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ async function bootstrap() {
|
|||
AppModule,
|
||||
fastifyAdapter,
|
||||
{
|
||||
bufferLogs: true,
|
||||
bufferLogs: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -57,11 +57,7 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||
// These are the permissions the user has
|
||||
const userPermissions = await this.usersService.getPermissions(user.id);
|
||||
if (HasFailed(userPermissions)) {
|
||||
throw Fail(
|
||||
FT.Internal,
|
||||
undefined,
|
||||
'Fetching user permissions failed: ' + userPermissions.getReason(),
|
||||
);
|
||||
throw userPermissions
|
||||
}
|
||||
|
||||
context.switchToHttp().getRequest().userPermissions = userPermissions;
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
import { Controller, Get, Request } from '@nestjs/common';
|
||||
import { UserInfoResponse } from 'picsur-shared/dist/dto/api/user-manage.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
|
||||
import { Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { NoPermissions, RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||
import { ApikeyDbService } from '../../../collections/apikey-db/apikey-db.service';
|
||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||
import { ReqUserID } from '../../../decorators/request-user.decorator';
|
||||
import { Returns } from '../../../decorators/returns.decorator';
|
||||
import type AuthFasityRequest from '../../../models/interfaces/authrequest.dto';
|
||||
|
||||
@Controller('api/experiment')
|
||||
@NoPermissions()
|
||||
@RequiredPermissions(Permission.Settings)
|
||||
@RequiredPermissions(Permission.SysPrefAdmin)
|
||||
export class ExperimentController {
|
||||
constructor(
|
||||
private readonly apikeyDB: ApikeyDbService,
|
||||
){}
|
||||
|
||||
@Get()
|
||||
@Returns(UserInfoResponse)
|
||||
async testRoute(
|
||||
@Request() req: AuthFasityRequest,
|
||||
@ReqUserID() thing: string,
|
||||
): Promise<UserInfoResponse> {
|
||||
throw Fail(FT.NotFound, new Error("hello"));
|
||||
const key = await this.apikeyDB.findOne("0SB7nCIkfhnAmf3Glejf0naUbI7dimhh", undefined);
|
||||
|
||||
console.log(key);
|
||||
|
||||
return req.user;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ApikeyDbModule } from '../../../collections/apikey-db/apikey-db.module';
|
||||
import { ExperimentController } from './experiment.controller';
|
||||
|
||||
// This is comletely useless module, but is used for testing
|
||||
// TODO: remove when out of beta
|
||||
|
||||
@Module({
|
||||
imports: [ApikeyDbModule],
|
||||
controllers: [ExperimentController],
|
||||
})
|
||||
export class ExperimentModule {}
|
||||
|
|
|
@ -30,7 +30,7 @@ enum PicsurImgState {
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PicsurImgComponent implements OnChanges {
|
||||
private readonly logger = new Logger(ZodImgComponent.name);
|
||||
private readonly logger = new Logger(PicsurImgComponent.name);
|
||||
|
||||
@ViewChild('targetcanvas') private canvas: ElementRef<HTMLCanvasElement>;
|
||||
@ViewChild('targetimg') private img: ElementRef<HTMLImageElement>;
|
||||
|
|
|
@ -63,7 +63,7 @@ export class UserAdminService {
|
|||
);
|
||||
}
|
||||
|
||||
public async deleteUser(id: string): AsyncFailable<EUser> {
|
||||
public async deleteUser(id: string): AsyncFailable<Omit<EUser, 'id'>> {
|
||||
return await this.api.post(
|
||||
UserDeleteRequest,
|
||||
UserDeleteResponse,
|
||||
|
|
|
@ -33,7 +33,7 @@ export class UserCreateResponse extends createZodDto(
|
|||
export const UserDeleteRequestSchema = EntityIDObjectSchema;
|
||||
export class UserDeleteRequest extends createZodDto(UserDeleteRequestSchema) {}
|
||||
|
||||
export const UserDeleteResponseSchema = EUserSchema;
|
||||
export const UserDeleteResponseSchema = EUserSchema.partial({ id: true });
|
||||
export class UserDeleteResponse extends createZodDto(
|
||||
UserDeleteResponseSchema,
|
||||
) {}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IsEntityID } from '../validators/entity-id.validator';
|
|||
|
||||
export const EApiKeySchema = z.object({
|
||||
key: z.string(),
|
||||
user_id: IsEntityID(),
|
||||
user: IsEntityID(),
|
||||
created: z.preprocess((data: any) => new Date(data), z.date()),
|
||||
});
|
||||
export type EApiKey = z.infer<typeof EApiKeySchema>;
|
||||
|
|
Loading…
Reference in a new issue