Don't use relations

This commit is contained in:
Dominik Korsa 2021-02-11 21:41:41 +01:00
parent 866ffc55c7
commit 0dd43c08b9
No known key found for this signature in database
GPG key ID: 546F986F71A6FE6E
17 changed files with 811 additions and 402 deletions

1024
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@
"lint": "eslint src -c .eslintrc.json --ext .ts",
"lint:fix": "eslint src -c .eslintrc.json --ext .ts --fix",
"start": "ts-node .",
"dev": "ts-node-dev ."
"dev": "nodemon --watch \"src/**\" --ext \"ts,json\" --exec \"npm run start\""
},
"author": "Dominik Korsa <dominik.korsa@gmail.com>",
"license": "MIT",
@ -49,6 +49,6 @@
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-typescript": "^12.0.0",
"eslint-plugin-import": "^2.22.1",
"ts-node-dev": "^1.1.1"
"nodemon": "^2.0.7"
}
}

View file

@ -1,7 +1,7 @@
import { addSeconds, isAfter } from 'date-fns';
import type { FastifyLoggerInstance } from 'fastify';
import { nanoid } from 'nanoid';
import type User from './database/entities/user';
import type { ObjectID } from 'typeorm';
import { UnknownCodeError } from './errors';
import type { CodeChallenge, CodeContent, CodeInfo } from './types';
import { createKey, decryptSymmetrical, encryptSymmetrical } from './utils';
@ -12,7 +12,7 @@ export function createCode(options: {
clientId: string;
scopes: string[];
studentIds: number[];
user: User;
userId: ObjectID;
publicKey: string;
tokenKey: string;
encryptedPrivateKey: string;
@ -31,7 +31,7 @@ export function createCode(options: {
expires,
id,
clientId: options.clientId,
user: options.user,
userId: options.userId,
scopes: options.scopes,
studentIds: options.studentIds,
publicKey: options.publicKey,

View file

@ -1,6 +1,7 @@
import type { Connection, Repository } from 'typeorm';
import type { Connection } from 'typeorm';
import { createConnection } from 'typeorm';
import Application from './entities/application';
import Client from './entities/client';
import Developer from './entities/developer';
import Token from './entities/token';
import User from './entities/user';
@ -8,14 +9,6 @@ import User from './entities/user';
class Database {
private connection!: Connection;
public applicationRepo!: Repository<Application>;
public userRepo!: Repository<User>;
public tokenRepo!: Repository<Token>;
public developerRepo!: Repository<Developer>;
public async connect(): Promise<void> {
this.connection = await createConnection({
type: 'mongodb',
@ -27,14 +20,11 @@ class Database {
User,
Token,
Developer,
Client,
],
useUnifiedTopology: true,
logging: false,
});
this.applicationRepo = this.connection.getRepository(Application);
this.userRepo = this.connection.getRepository(User);
this.tokenRepo = this.connection.getRepository(Token);
this.developerRepo = this.connection.getRepository(Developer);
}
}

View file

@ -1,22 +1,14 @@
import { nanoid } from 'nanoid';
import type { ObjectID } from 'typeorm';
import {
BaseEntity,
Column, Entity, ManyToOne, ObjectIdColumn,
Column, Entity, ObjectIdColumn,
} from 'typeorm';
import Developer from './developer';
@Entity()
export default class Application extends BaseEntity {
@ObjectIdColumn()
public _id!: ObjectID;
@Column()
public clientId!: string;
@Column()
public clientSecret!: string;
@Column()
public name!: string;
@ -30,15 +22,8 @@ export default class Application extends BaseEntity {
public verified!: boolean;
@Column()
public redirectUris!: string[];
@ManyToOne(() => Developer)
public developer!: Developer;
public developerId!: ObjectID;
@Column()
public homepage!: string | null;
public static generateClientId(): string {
return nanoid(12);
}
}

View file

@ -0,0 +1,34 @@
import { nanoid } from 'nanoid';
import type { ObjectID } from 'typeorm';
import {
BaseEntity, Column, Entity, ObjectIdColumn,
} from 'typeorm';
@Entity()
export default class Client extends BaseEntity {
@ObjectIdColumn()
public _id!: ObjectID;
@Column()
public clientId!: string;
@Column()
public clientSecret!: string;
@Column()
public name!: string;
@Column()
public redirectUris!: string[];
@Column()
public applicationId!: ObjectID;
public static generateClientId(): string {
return nanoid(12);
}
public static generateClientSecret(): string {
return nanoid(32);
}
}

View file

@ -1,9 +1,8 @@
import { nanoid } from 'nanoid';
import type { ObjectID } from 'typeorm';
import {
BaseEntity, Column, Entity, ManyToOne, ObjectIdColumn,
BaseEntity, Column, Entity, ObjectIdColumn,
} from 'typeorm';
import User from './user';
@Entity()
export default class Token extends BaseEntity {
@ -25,8 +24,8 @@ export default class Token extends BaseEntity {
@Column()
public clientId!: string;
@ManyToOne(() => User)
public user!: User;
@Column()
public userId!: ObjectID;
@Column()
public tokenSecret!: string;

View file

@ -2,7 +2,7 @@ import _ from 'lodash';
import { nanoid } from 'nanoid';
import urlJoin from 'url-join';
import { scopes, websitePrefix } from '../../constants';
import database from '../../database/database';
import Client from '../../database/entities/client';
import { ParamError, ScopeError } from '../../errors';
import type { MyFastifyInstance, StudentsMode } from '../../types';
@ -42,16 +42,16 @@ export default function registerAuthorize(server: MyFastifyInstance): void {
return;
}
const application = await database.applicationRepo.findOne({
const client = await Client.findOne({
where: {
clientId: request.query.client_id,
},
});
if (application === undefined) {
if (client === undefined) {
await reply.redirect(urlJoin(websitePrefix, '/prompt-error?code=unknown_application'));
return;
}
if (!application.redirectUris.includes(request.query.redirect_uri)) {
if (!client.redirectUris.includes(request.query.redirect_uri)) {
await reply.redirect(urlJoin(websitePrefix, '/prompt-error?code=unknown_redirect_uri'));
return;
}
@ -88,7 +88,7 @@ export default function registerAuthorize(server: MyFastifyInstance): void {
const sessionData = getSessionData(request.session);
sessionData.authPrompts.set(promptId, {
clientId: request.query.client_id,
clientId: client.clientId,
redirectUri: request.query.redirect_uri,
scopes: requestedScopes,
state: request.query.state,

View file

@ -1,6 +1,6 @@
import type { FastifyReply } from 'fastify';
import { getCode, invalidateCode } from '../../codes';
import database from '../../database/database';
import Client from '../../database/entities/client';
import Token from '../../database/entities/token';
import { ParamError } from '../../errors';
import type { CodeInfo, MyFastifyInstance, TokenContent } from '../../types';
@ -67,13 +67,13 @@ export default function registerToken(server: MyFastifyInstance): void {
return;
}
const application = await database.applicationRepo.findOne({
const client = await Client.findOne({
where: {
clientId: request.body.client_id,
},
});
if (!application) {
await sendCustomError(reply, 'invalid_client', 'Application not found', 401);
if (!client) {
await sendCustomError(reply, 'invalid_client', 'Client id not found', 401);
return;
}
@ -96,7 +96,7 @@ export default function registerToken(server: MyFastifyInstance): void {
}
} else {
validateParam('client_secret', request.body.client_secret);
if (application.clientSecret !== request.body.client_secret) {
if (client.clientSecret !== request.body.client_secret) {
await sendCustomError(reply, 'invalid_client', 'Invalid client secret', 401);
return;
}
@ -111,13 +111,13 @@ export default function registerToken(server: MyFastifyInstance): void {
token.scopes = codeInfo.scopes;
token.studentIds = codeInfo.studentIds;
token.tokenSecret = codeInfo.tokenSecret;
token.user = codeInfo.user;
token.userId = codeInfo.userId;
token.encryptedPassword = codeInfo.encryptedPassword;
token.encryptedPrivateKey = codeInfo.encryptedPrivateKey;
token.encryptedSDK = codeInfo.encryptedSDK;
token.publicKey = codeInfo.publicKey;
await database.tokenRepo.save(token);
await token.save();
const content: TokenContent = {
tk: tokenKey,

View file

@ -36,7 +36,7 @@ export default function registerAllow(server: MyFastifyInstance): void {
if (!prompt) throw server.httpErrors.badRequest('Prompt data not found');
if (!prompt.loginInfo) throw server.httpErrors.badRequest('Login data not provided');
if (!prompt.loginInfo.symbolInfo) throw server.httpErrors.badRequest('Symbol not provided');
if (!prompt.loginInfo.symbolInfo.user) throw server.httpErrors.badRequest('User not registered');
if (!prompt.loginInfo.symbolInfo.userId) throw server.httpErrors.badRequest('User not registered');
const tokenKey = decryptSymmetrical(encryptedTokenKey, prompt.promptSecret);
const serializedClient = JSON.parse(decryptSymmetrical(prompt.loginInfo.encryptedClient, tokenKey)) as SerializedClient;
@ -67,7 +67,7 @@ export default function registerAllow(server: MyFastifyInstance): void {
studentIds,
scopes: prompt.scopes,
clientId: prompt.clientId,
user: prompt.loginInfo.symbolInfo.user,
userId: prompt.loginInfo.symbolInfo.userId,
publicKey: prompt.loginInfo.publicKey,
encryptedSDK: newEncryptedSDK,
encryptedPassword: prompt.loginInfo.encryptedPassword,

View file

@ -10,9 +10,6 @@ export default class PromptInfo {
@Field(() => [String])
public scopes!: string[];
@Field(() => String)
public clientId!: string;
@Field(() => StudentsMode)
public studentsMode!: StudentsMode;

View file

@ -3,7 +3,6 @@ import { UserInputError } from 'apollo-server-fastify';
import {
Arg, Ctx, Mutation, Resolver,
} from 'type-graphql';
import database from '../../../../database/database';
import User from '../../../../database/entities/user';
import { UnknownPromptError } from '../../errors';
import CreateUserResult from '../../models/create-user-result';
@ -23,7 +22,7 @@ export default class CreateUserResolver {
if (!prompt.loginInfo) throw new UserInputError('Login data not provided');
if (!prompt.loginInfo.symbolInfo) throw new UserInputError('Symbol not provided');
const existingUser = await database.userRepo.findOne({
const existingUser = await User.findOne({
where: {
host: prompt.loginInfo.host,
symbol: prompt.loginInfo.symbolInfo.symbol,
@ -39,8 +38,8 @@ export default class CreateUserResolver {
user.username = prompt.loginInfo.username;
user.loginIds = prompt.loginInfo.symbolInfo.loginIds;
user.email = email;
await database.userRepo.save(user);
prompt.loginInfo.symbolInfo.user = user;
await user.save();
prompt.loginInfo.symbolInfo.userId = user._id;
return {
success: true,
};

View file

@ -1,48 +1,47 @@
/* eslint-disable class-methods-use-this */
import type { ResolverInterface } from 'type-graphql';
import { ApolloError } from 'apollo-server-fastify';
import {
Arg, Ctx, FieldResolver, Query, Resolver, Root,
Arg, Ctx, Query, Resolver,
} from 'type-graphql';
import database from '../../../../database/database';
import Application from '../../../../database/entities/application';
import Client from '../../../../database/entities/client';
import Developer from '../../../../database/entities/developer';
import { getUser } from '../../../../graphql/github/sdk';
import { UnknownPromptError } from '../../errors';
import PromptInfo from '../../models/prompt-info';
import type PromptInfoApplication from '../../models/prompt-info-application';
import type { WebsiteAPIContext } from '../../types';
@Resolver(PromptInfo)
export default class PromptInfoResolver implements ResolverInterface<PromptInfo> {
export default class PromptInfoResolver {
@Query(() => PromptInfo)
public promptInfo(
public async promptInfo(
@Arg('promptId') promptId: string,
@Ctx() { sessionData }: WebsiteAPIContext,
): Partial<PromptInfo> {
): Promise<Partial<PromptInfo>> {
const prompt = sessionData.authPrompts.get(promptId);
if (!prompt) throw new UnknownPromptError();
return {
id: promptId,
clientId: prompt.clientId,
scopes: prompt.scopes,
studentsMode: prompt.studentsMode,
};
}
@FieldResolver()
public async application(@Root() prompt: PromptInfo): Promise<PromptInfoApplication> {
const application = await database.applicationRepo.findOne({
const client = await Client.findOne({
where: {
clientId: prompt.clientId,
},
relations: ['developer'],
});
if (!application) throw new Error('Prompt data not found');
if (!client) throw new ApolloError('Client not found');
const application = await Application.findOne(client.applicationId);
if (!application) throw new ApolloError('Application not found');
const developer = await Developer.findOne(application.developerId);
if (!developer) throw new ApolloError('Developer not found');
return {
name: application.name,
iconUrl: application.iconUrl,
iconColor: application.iconColor,
verified: application.verified,
homepage: application.homepage,
developer: await getUser(application.developer.gitHubLogin),
id: promptId,
scopes: prompt.scopes,
studentsMode: prompt.studentsMode,
application: {
name: application.name,
iconUrl: application.iconUrl,
iconColor: application.iconColor,
verified: application.verified,
homepage: application.homepage,
developer: await getUser(developer.gitHubLogin),
},
};
}
}

View file

@ -7,7 +7,6 @@ import _ from 'lodash';
import {
Arg, Ctx, Mutation, Resolver,
} from 'type-graphql';
import database from '../../../../database/database';
import User from '../../../../database/entities/user';
import { decryptSymmetrical, decryptWithPrivateKey, encryptSymmetrical } from '../../../../utils';
import { InvalidSymbolError, UnknownPromptError } from '../../errors';
@ -64,7 +63,7 @@ export default class SetSymbolResolver {
const encryptedClient = encryptSymmetrical(JSON.stringify(client.serialize()), tokenKey);
const encryptedDiaries = encryptSymmetrical(JSON.stringify(diaryList.map(({ serialized }) => serialized)), tokenKey);
const user = await database.userRepo.findOne({
const user = await User.findOne({
where: {
host: prompt.loginInfo.host,
symbol,
@ -86,7 +85,7 @@ export default class SetSymbolResolver {
encryptedDiaries,
loginIds,
availableStudentIds: students.map(({ studentId }) => studentId),
user,
userId: user?._id,
};
return {
students,

View file

@ -9,7 +9,7 @@ import type {
RawServerDefault,
} from 'fastify';
import { registerEnumType } from 'type-graphql';
import type User from './database/entities/user';
import type { ObjectID } from 'typeorm';
import type SessionData from './session-data';
export enum StudentsMode {
@ -47,7 +47,7 @@ export interface AuthPrompt {
encryptedDiaries: string;
availableStudentIds: number[];
loginIds: string[];
user?: User,
userId?: ObjectID,
}
};
}
@ -77,7 +77,7 @@ export interface CodeInfo {
expires: Date;
scopes: string[];
clientId: string;
user: User,
userId: ObjectID,
studentIds: number[];
tokenSecret: string;
publicKey: string;

View file

@ -54,7 +54,7 @@
</v-list-item-content>
</v-list-item>
<v-list-item
:href="promptInfo.application.owner.url"
:href="promptInfo.application.developer.url"
target="_blank"
rel="noopener"
>

View file

@ -28,7 +28,6 @@ export type PromptInfo = {
__typename?: 'PromptInfo';
id: Scalars['String'];
scopes: Array<Scalars['String']>;
clientId: Scalars['String'];
studentsMode: StudentsMode;
application: PromptInfoApplication;
};