Don't use relations
This commit is contained in:
parent
866ffc55c7
commit
0dd43c08b9
17 changed files with 811 additions and 402 deletions
1024
backend/package-lock.json
generated
1024
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
34
backend/src/database/entities/client.ts
Normal file
34
backend/src/database/entities/client.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -10,9 +10,6 @@ export default class PromptInfo {
|
|||
@Field(() => [String])
|
||||
public scopes!: string[];
|
||||
|
||||
@Field(() => String)
|
||||
public clientId!: string;
|
||||
|
||||
@Field(() => StudentsMode)
|
||||
public studentsMode!: StudentsMode;
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -28,7 +28,6 @@ export type PromptInfo = {
|
|||
__typename?: 'PromptInfo';
|
||||
id: Scalars['String'];
|
||||
scopes: Array<Scalars['String']>;
|
||||
clientId: Scalars['String'];
|
||||
studentsMode: StudentsMode;
|
||||
application: PromptInfoApplication;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue