icynet-auth-server/src/modules/objects/oauth2-client/oauth2-client.service.ts

347 lines
8.5 KiB
TypeScript

import { Inject, Injectable } from '@nestjs/common';
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
import { ILike, Repository } from 'typeorm';
import { Upload } from '../upload/upload.entity';
import { UploadService } from '../upload/upload.service';
import { User } from '../user/user.entity';
import { OAuth2ClientAuthorization } from './oauth2-client-authorization.entity';
import {
OAuth2ClientURL,
OAuth2ClientURLType,
} from './oauth2-client-url.entity';
import { OAuth2Client } from './oauth2-client.entity';
@Injectable()
export class OAuth2ClientService {
public availableGrantTypes = [
'authorization_code',
'refresh_token',
'id_token',
'implicit',
];
public availableScopes = [
'picture',
'profile',
'email',
'privileges',
'management',
'openid',
];
constructor(
@Inject('CLIENT_REPOSITORY')
private clientRepository: Repository<OAuth2Client>,
@Inject('CLIENT_URL_REPOSITORY')
private clientUrlRepository: Repository<OAuth2ClientURL>,
@Inject('CLIENT_AUTHORIZATION_REPOSITORY')
private clientAuthRepository: Repository<OAuth2ClientAuthorization>,
private _upload: UploadService,
private _form: FormUtilityService,
) {}
public async hasAuthorized(
userId: number,
clientId: string,
scope: string[],
): Promise<boolean> {
const authorization = await this.clientAuthRepository.findOne({
where: {
user: {
id: userId,
},
client: {
client_id: clientId,
},
},
relations: ['user', 'client'],
});
if (!authorization) {
return false;
}
// Scopes must have been allowed
const splitScope = authorization.scope.split(' ');
if (scope.every((item) => splitScope.includes(item))) {
return true;
}
return false;
}
public async createAuthorization(
user: User,
client: OAuth2Client,
scope: string[],
): Promise<OAuth2ClientAuthorization> {
const existing = await this.clientAuthRepository.findOne({
where: {
user: { id: user.id },
client: { id: client.id },
},
relations: ['user', 'client'],
});
if (existing) {
const splitScope = existing.scope.split(' ');
scope.forEach((item) => {
if (!splitScope.includes(item)) {
splitScope.push(item);
}
});
existing.scope = splitScope.join(' ');
await this.clientAuthRepository.save(existing);
return existing;
}
const authorization = new OAuth2ClientAuthorization();
authorization.user = user;
authorization.client = client;
authorization.scope = scope.join(' ');
await this.clientAuthRepository.insert(authorization);
return authorization;
}
public async getAuthorizations(
user: User,
): Promise<OAuth2ClientAuthorization[]> {
return this.clientAuthRepository.find({
relations: ['user', 'client', 'client.urls'],
where: { user: { id: user.id } },
});
}
public async revokeAuthorization(
auth: OAuth2ClientAuthorization,
): Promise<OAuth2ClientAuthorization> {
return this.clientAuthRepository.remove(auth);
}
public async getAuthorization(
user: User,
authId: number,
): Promise<OAuth2ClientAuthorization> {
return this.clientAuthRepository.findOne({
where: {
user: { id: user.id },
id: authId,
},
relations: ['user', 'client'],
});
}
public async getById(
id: string | number,
relations = ['urls', 'picture'],
): Promise<OAuth2Client> {
if (!id) {
return null;
}
let client: OAuth2Client;
if (typeof id === 'string') {
client = await this.clientRepository.findOne({
where: { client_id: id },
relations,
});
} else {
client = await this.clientRepository.findOne({
where: { id },
relations,
});
}
return client;
}
public async searchClients(
limit = 50,
offset = 0,
search?: string,
relations?: string[],
): Promise<[OAuth2Client[], number]> {
return this.clientRepository.findAndCount({
where: search
? [
{
title: ILike(`%${search}%`),
},
{
client_id: search,
},
]
: undefined,
skip: offset,
take: limit,
order: { id: 'asc' },
relations,
});
}
public async searchClientsCount(
search?: string,
relations?: string[],
): Promise<number> {
return this.clientRepository.count({
where: search
? [
{
title: ILike(`%${search}%`),
},
{
client_id: search,
},
]
: undefined,
relations,
});
}
public async getClientsByOwner(
owner: User,
relations?: string[],
): Promise<OAuth2Client[]> {
return this.clientRepository.find({
where: { owner: { id: owner.id } },
order: { id: 'asc' },
relations,
});
}
public async getClientURLs(
id: string,
type?: OAuth2ClientURLType,
): Promise<OAuth2ClientURL[]> {
return this.clientUrlRepository.find({
where: {
client: { client_id: id },
type,
},
relations: ['client'],
});
}
public async getClientURLById(id: number): Promise<OAuth2ClientURL> {
return this.clientUrlRepository.findOne({
where: {
id,
},
relations: ['client'],
});
}
public async checkRedirectURI(id: string, url: string): Promise<boolean> {
return !!(await this.clientUrlRepository.findOne({
where: {
client: { client_id: id },
url,
type: OAuth2ClientURLType.REDIRECT_URI,
},
relations: ['client'],
}));
}
public async upsertURLs(
client: OAuth2Client,
urls: OAuth2ClientURL[],
): Promise<OAuth2Client> {
const existingURLs = await this.getClientURLs(client.client_id);
const removed = [];
for (const existing of existingURLs) {
const alsoProvided = urls.find(({ id }) => id === existing.id);
if (alsoProvided && !!alsoProvided.url) {
Object.assign(existing, alsoProvided);
await this.updateClientURL(existing);
} else {
await this.deleteClientURL(existing);
removed.push(existing);
}
}
for (const newUrl of urls.filter((url) => !url.id)) {
const newUrlObject = this.reobjectifyURL(newUrl, client);
await this.updateClientURL(newUrlObject);
existingURLs.push(newUrlObject);
}
client.urls = existingURLs
.filter(({ id }) => !removed.some((removed) => removed.id === id))
.map((itm) => ({ ...itm, client: undefined }));
return client;
}
public async updateClient(client: OAuth2Client): Promise<OAuth2Client> {
await this.clientRepository.save(client);
return client;
}
public async updateClientURL(url: OAuth2ClientURL): Promise<OAuth2ClientURL> {
await this.clientUrlRepository.save(url);
return url;
}
public async deleteClientURL(url: OAuth2ClientURL): Promise<void> {
await this.clientUrlRepository.remove(url);
}
public async deleteClient(client: OAuth2Client): Promise<void> {
await this.clientRepository.remove(client);
}
public async updatePicture(
client: OAuth2Client,
upload: Upload,
): Promise<OAuth2Client> {
if (client.picture) {
await this._upload.delete(client.picture);
}
client.picture = upload;
await this.updateClient(client);
return client;
}
public async wipeClientAuthorizations(client: OAuth2Client): Promise<void> {
await this.clientAuthRepository.delete({
client: { id: client.id },
});
}
public async deletePicture(client: OAuth2Client): Promise<void> {
if (client.picture) {
await this._upload.delete(client.picture);
}
}
public stripClientInfo(client: OAuth2Client): Partial<OAuth2Client> {
return {
...client,
owner: client.owner
? this._form.pluckObject(client.owner, ['id', 'uuid', 'username'])
: null,
picture: client.picture
? this._form.pluckObject(client.picture, [
'id',
'mimetype',
'file',
'created_at',
])
: null,
} as Partial<OAuth2Client>;
}
private reobjectifyURL(
input: Partial<OAuth2ClientURL>,
client: OAuth2Client,
): OAuth2ClientURL {
const reObjectifyURL = new OAuth2ClientURL();
Object.assign(reObjectifyURL, input);
reObjectifyURL.client = client;
return reObjectifyURL;
}
}