2022-03-09 18:37:04 +00:00
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
2022-08-29 14:51:20 +00:00
|
|
|
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
|
2022-08-27 15:52:37 +00:00
|
|
|
import { ILike, Repository } from 'typeorm';
|
|
|
|
import { Upload } from '../upload/upload.entity';
|
|
|
|
import { UploadService } from '../upload/upload.service';
|
2022-03-20 14:50:12 +00:00
|
|
|
import { User } from '../user/user.entity';
|
2022-03-09 18:37:04 +00:00
|
|
|
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 {
|
2022-08-27 15:52:37 +00:00
|
|
|
public availableGrantTypes = [
|
|
|
|
'authorization_code',
|
|
|
|
'refresh_token',
|
|
|
|
'id_token',
|
|
|
|
'implicit',
|
|
|
|
];
|
|
|
|
|
|
|
|
public availableScopes = ['image', 'email', 'privileges', 'management'];
|
|
|
|
|
2022-03-09 18:37:04 +00:00
|
|
|
constructor(
|
|
|
|
@Inject('CLIENT_REPOSITORY')
|
|
|
|
private clientRepository: Repository<OAuth2Client>,
|
|
|
|
@Inject('CLIENT_URL_REPOSITORY')
|
|
|
|
private clientUrlRepository: Repository<OAuth2ClientURL>,
|
|
|
|
@Inject('CLIENT_AUTHORIZATION_REPOSITORY')
|
|
|
|
private clientAuthRepository: Repository<OAuth2ClientAuthorization>,
|
2022-08-27 15:52:37 +00:00
|
|
|
private _upload: UploadService,
|
2022-08-29 14:51:20 +00:00
|
|
|
private _form: FormUtilityService,
|
2022-03-09 18:37:04 +00:00
|
|
|
) {}
|
|
|
|
|
2022-03-20 14:50:12 +00:00
|
|
|
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: {
|
2022-08-27 08:59:26 +00:00
|
|
|
user: { id: user.id },
|
|
|
|
client: { id: client.id },
|
2022-03-20 14:50:12 +00:00
|
|
|
},
|
|
|
|
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'],
|
2022-08-27 08:59:26 +00:00
|
|
|
where: { user: { id: user.id } },
|
2022-03-20 14:50:12 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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: {
|
2022-08-27 08:59:26 +00:00
|
|
|
user: { id: user.id },
|
2022-03-20 14:50:12 +00:00
|
|
|
id: authId,
|
|
|
|
},
|
2022-08-28 13:17:15 +00:00
|
|
|
relations: ['user', 'client'],
|
2022-03-20 14:50:12 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:52:37 +00:00
|
|
|
public async getById(
|
|
|
|
id: string | number,
|
|
|
|
relations = ['urls', 'picture'],
|
|
|
|
): Promise<OAuth2Client> {
|
|
|
|
if (!id) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-03-09 18:37:04 +00:00
|
|
|
let client: OAuth2Client;
|
|
|
|
|
|
|
|
if (typeof id === 'string') {
|
2022-08-17 19:48:20 +00:00
|
|
|
client = await this.clientRepository.findOne({
|
|
|
|
where: { client_id: id },
|
2022-08-27 15:52:37 +00:00
|
|
|
relations,
|
2022-08-17 19:48:20 +00:00
|
|
|
});
|
2022-03-09 18:37:04 +00:00
|
|
|
} else {
|
2022-08-17 19:48:20 +00:00
|
|
|
client = await this.clientRepository.findOne({
|
|
|
|
where: { id },
|
2022-08-27 15:52:37 +00:00
|
|
|
relations,
|
2022-08-17 19:48:20 +00:00
|
|
|
});
|
2022-03-09 18:37:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return client;
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:52:37 +00:00
|
|
|
public async searchClients(
|
|
|
|
limit = 50,
|
|
|
|
offset = 0,
|
|
|
|
search?: string,
|
|
|
|
relations?: string[],
|
|
|
|
): Promise<[OAuth2Client[], number]> {
|
|
|
|
return this.clientRepository.findAndCount({
|
|
|
|
where: search
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
title: ILike(`%${search}%`),
|
|
|
|
},
|
2022-08-27 16:58:24 +00:00
|
|
|
{
|
|
|
|
client_id: search,
|
|
|
|
},
|
2022-08-27 15:52:37 +00:00
|
|
|
]
|
|
|
|
: undefined,
|
|
|
|
skip: offset,
|
|
|
|
take: limit,
|
2022-09-01 17:19:08 +00:00
|
|
|
order: { id: 'asc' },
|
2022-08-27 15:52:37 +00:00
|
|
|
relations,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async searchClientsCount(
|
|
|
|
search?: string,
|
|
|
|
relations?: string[],
|
|
|
|
): Promise<number> {
|
|
|
|
return this.clientRepository.count({
|
|
|
|
where: search
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
title: ILike(`%${search}%`),
|
|
|
|
},
|
2022-08-27 16:58:24 +00:00
|
|
|
{
|
|
|
|
client_id: search,
|
|
|
|
},
|
2022-08-27 15:52:37 +00:00
|
|
|
]
|
|
|
|
: undefined,
|
|
|
|
relations,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-08-27 16:58:24 +00:00
|
|
|
public async getClientsByOwner(
|
|
|
|
owner: User,
|
|
|
|
relations?: string[],
|
|
|
|
): Promise<OAuth2Client[]> {
|
|
|
|
return this.clientRepository.find({
|
|
|
|
where: { owner: { id: owner.id } },
|
2022-09-01 17:19:08 +00:00
|
|
|
order: { id: 'asc' },
|
2022-08-27 16:58:24 +00:00
|
|
|
relations,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-09 18:37:04 +00:00
|
|
|
public async getClientURLs(
|
|
|
|
id: string,
|
|
|
|
type?: OAuth2ClientURLType,
|
|
|
|
): Promise<OAuth2ClientURL[]> {
|
2022-03-16 18:37:50 +00:00
|
|
|
return this.clientUrlRepository.find({
|
|
|
|
where: {
|
|
|
|
client: { client_id: id },
|
|
|
|
type,
|
|
|
|
},
|
|
|
|
relations: ['client'],
|
|
|
|
});
|
2022-03-09 18:37:04 +00:00
|
|
|
}
|
|
|
|
|
2022-08-27 15:52:37 +00:00
|
|
|
public async getClientURLById(id: number): Promise<OAuth2ClientURL> {
|
|
|
|
return this.clientUrlRepository.findOne({
|
|
|
|
where: {
|
|
|
|
id,
|
|
|
|
},
|
|
|
|
relations: ['client'],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-09 18:37:04 +00:00
|
|
|
public async checkRedirectURI(id: string, url: string): Promise<boolean> {
|
2022-08-17 19:48:20 +00:00
|
|
|
return !!(await this.clientUrlRepository.findOne({
|
|
|
|
where: {
|
2022-03-16 18:37:50 +00:00
|
|
|
client: { client_id: id },
|
2022-09-11 12:49:14 +00:00
|
|
|
url,
|
2022-03-16 18:37:50 +00:00
|
|
|
type: OAuth2ClientURLType.REDIRECT_URI,
|
|
|
|
},
|
2022-08-17 19:48:20 +00:00
|
|
|
relations: ['client'],
|
|
|
|
}));
|
2022-03-09 18:37:04 +00:00
|
|
|
}
|
2022-08-27 15:52:37 +00:00
|
|
|
|
2022-09-01 17:19:08 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:52:37 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-09-09 14:37:21 +00:00
|
|
|
public async deleteClient(client: OAuth2Client): Promise<void> {
|
|
|
|
await this.clientRepository.remove(client);
|
|
|
|
}
|
|
|
|
|
2022-08-27 15:52:37 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2022-08-29 14:51:20 +00:00
|
|
|
|
|
|
|
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>;
|
|
|
|
}
|
2022-09-01 17:19:08 +00:00
|
|
|
|
|
|
|
private reobjectifyURL(
|
|
|
|
input: Partial<OAuth2ClientURL>,
|
|
|
|
client: OAuth2Client,
|
|
|
|
): OAuth2ClientURL {
|
|
|
|
const reObjectifyURL = new OAuth2ClientURL();
|
|
|
|
Object.assign(reObjectifyURL, input);
|
|
|
|
reObjectifyURL.client = client;
|
|
|
|
return reObjectifyURL;
|
|
|
|
}
|
2022-03-09 18:37:04 +00:00
|
|
|
}
|