implemented groups and password hashing

- added Groups with creator, admins, members, groupChats, name
- implemented hashing before storing the password in the database
pull/2/head
Trivernis 5 years ago
parent ba828da18a
commit 97ed21e469

@ -1,8 +1,10 @@
import {GraphQLError} from "graphql"; import {GraphQLError} from "graphql";
import * as status from "http-status"; import * as status from "http-status";
import dataaccess from "../lib/dataaccess"; import dataaccess from "../lib/dataaccess";
import {UserNotFoundError} from "../lib/errors/UserNotFoundError";
import {Group} from "../lib/models";
import * as models from "../lib/models"; import * as models from "../lib/models";
import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import {GroupNotFoundGqlError, NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors";
import globals from "../lib/globals"; import globals from "../lib/globals";
import {InternalEvents} from "../lib/InternalEvents"; import {InternalEvents} from "../lib/InternalEvents";
import {is} from "../lib/regex"; import {is} from "../lib/regex";
@ -106,7 +108,7 @@ export function resolver(req: any, res: any): any {
if (post) { if (post) {
return await post.vote(req.session.userId, type); return await post.vote(req.session.userId, type);
} else { } else {
res.status(400); res.status(status.BAD_REQUEST);
return new PostNotFoundGqlError(postId); return new PostNotFoundGqlError(postId);
} }
} else { } else {
@ -229,5 +231,99 @@ export function resolver(req: any, res: any): any {
async getPosts({first, offset, sort}: {first: number, offset: number, sort: dataaccess.SortType}) { async getPosts({first, offset, sort}: {first: number, offset: number, sort: dataaccess.SortType}) {
return await dataaccess.getPosts(first, offset, sort); return await dataaccess.getPosts(first, offset, sort);
}, },
async createGroup({name, members}: {name: string, members: number[]}) {
if (req.session.userId) {
return await dataaccess.createGroup(name, req.session.userId, members);
} else {
return new NotLoggedInGqlError();
}
},
async joinGroup({id}: {id: number}) {
if (req.session.userId) {
const group = await models.Group.findByPk(id);
if (group) {
const user = await models.User.findByPk(req.session.userId);
await group.$add("rMembers", user);
await (await group.chat()).$add("rMembers", user);
return group;
} else {
res.status(status.BAD_REQUEST);
return new GroupNotFoundGqlError(id);
}
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async leaveGroup({id}: {id: number}) {
if (req.session.userId) {
const group = await models.Group.findByPk(id);
if (group) {
const user = await models.User.findByPk(req.session.userId);
await group.$remove("rMembers", user);
await (await group.chat()).$remove("rMembers", user);
return group;
} else {
res.status(status.BAD_REQUEST);
return new GroupNotFoundGqlError(id);
}
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async addGroupAdmin({groupId, userId}: {groupId: number, userId: number}) {
if (req.session.userId) {
const group = await models.Group.findByPk(groupId);
const user = await models.User.findByPk(userId);
const self = await models.User.findByPk(req.session.userId);
if (!group) {
res.status(status.BAD_REQUEST);
return new GroupNotFoundGqlError(groupId);
}
if (!user) {
res.status(status.BAD_REQUEST);
return new UserNotFoundError(userId.toString()).graphqlError;
}
if (!(await group.$has("rAdmins", self)) && (await group.creator()) !== self.id) {
res.status(status.FORBIDDEN);
return new GraphQLError("You are not a group admin!");
}
await group.$add("rAdmins", user);
return group;
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
async removeGroupAdmin({groupId, userId}: {groupId: number, userId: number}) {
if (req.session.userId) {
const group = await models.Group.findByPk(groupId);
const user = await models.User.findByPk(userId);
if (!group) {
res.status(status.BAD_REQUEST);
return new GroupNotFoundGqlError(groupId);
}
if (!user) {
res.status(status.BAD_REQUEST);
return new UserNotFoundError(userId.toString()).graphqlError;
}
if ((await group.creator()).id === userId) {
res.status(status.FORBIDDEN);
return new GraphQLError("You can't remove the creator of a group.");
}
if ((await group.creator()).id !== req.session.userId) {
res.status(status.FORBIDDEN);
return new GraphQLError("You are not a group admin!");
}
await group.$remove("rAdmins", user);
return group;
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
}; };
} }

@ -60,6 +60,21 @@ type Mutation {
"Creates a chat between the user (and optional an other user)" "Creates a chat between the user (and optional an other user)"
createChat(members: [ID!]): ChatRoom createChat(members: [ID!]): ChatRoom
"Creates a new group with a given name and additional members"
createGroup(name: String!, members: [ID!]): Group
"Joins a group with the given id"
joinGroup(id: ID!): Group
"leaves the group with the given id"
leaveGroup(id: ID!): Group
"adds an admin to the group"
addGroupAdmin(groupId: ID!, userId: ID!): Group
"removes an admin from the group"
removeGroupAdmin(groupId: ID!, userId: ID!): Group
} }
interface UserData { interface UserData {
@ -148,6 +163,16 @@ type Profile implements UserData {
"all received request for groupChats/friends/events" "all received request for groupChats/friends/events"
receivedRequests: [Request] receivedRequests: [Request]
"all groups the user is an admin of"
administratedGroups: [Group]
"all groups the user has created"
createdGroups: [Group]
"all groups the user has joined"
groups: [Group]
} }
"represents a single user post" "represents a single user post"
@ -225,6 +250,26 @@ type ChatMessage {
htmlContent: String htmlContent: String
} }
type Group {
"ID of the group"
id: ID!
"name of the group"
name: String!
"the creator of the group"
creator: User
"all admins of the group"
admins: [User]!
"the members of the group with pagination"
members(first: Int = 10, offset: Int = 0): [User]!
"the groups chat"
chat: ChatRoom
}
"represents the type of vote performed on a post" "represents the type of vote performed on a post"
enum VoteType { enum VoteType {
UPVOTE UPVOTE

@ -1,3 +1,4 @@
import * as crypto from "crypto";
import {Sequelize} from "sequelize-typescript"; import {Sequelize} from "sequelize-typescript";
import {ChatNotFoundError} from "./errors/ChatNotFoundError"; import {ChatNotFoundError} from "./errors/ChatNotFoundError";
import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError"; import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError";
@ -36,6 +37,9 @@ namespace dataaccess {
models.PostVote, models.PostVote,
models.Request, models.Request,
models.User, models.User,
models.Group,
models.GroupAdmin,
models.GroupMember,
]); ]);
} catch (err) { } catch (err) {
globals.logger.error(err.message); globals.logger.error(err.message);
@ -62,6 +66,9 @@ namespace dataaccess {
* @param password * @param password
*/ */
export async function getUserByLogin(email: string, password: string): Promise<models.User> { export async function getUserByLogin(email: string, password: string): Promise<models.User> {
const hash = crypto.createHash("sha512");
hash.update(password);
password = hash.digest("hex");
const user = await models.User.findOne({where: {email, password}}); const user = await models.User.findOne({where: {email, password}});
if (user) { if (user) {
return user; return user;
@ -77,6 +84,9 @@ namespace dataaccess {
* @param password * @param password
*/ */
export async function registerUser(username: string, email: string, password: string): Promise<models.User> { export async function registerUser(username: string, email: string, password: string): Promise<models.User> {
const hash = crypto.createHash("sha512");
hash.update(password);
password = hash.digest("hex");
const existResult = !!(await models.User.findOne({where: {username, email, password}})); const existResult = !!(await models.User.findOne({where: {username, email, password}}));
const handle = generateHandle(username); const handle = generateHandle(username);
if (!existResult) { if (!existResult) {
@ -201,6 +211,27 @@ namespace dataaccess {
return request; return request;
} }
/**
* Create a new group.
* @param name
* @param creator
* @param members
*/
export async function createGroup(name: string, creator: number, members: number[]): Promise<models.Group> {
return sequelize.transaction(async (t) => {
members.push(creator);
const groupChat = await createChat(...members);
const group = await models.Group.create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t});
const creatorUser = await models.User.findByPk(creator, {transaction: t});
await group.$add("rAdmins", creatorUser, {transaction: t});
for (const member of members) {
const user = await models.User.findByPk(member, {transaction: t});
await group.$add("rMembers", user, {transaction: t});
}
return group;
});
}
/** /**
* Enum representing the types of votes that can be performed on a post. * Enum representing the types of votes that can be performed on a post.
*/ */

@ -11,3 +11,9 @@ export class PostNotFoundGqlError extends GraphQLError {
super(`Post '${postId}' not found!`); super(`Post '${postId}' not found!`);
} }
} }
export class GroupNotFoundGqlError extends GraphQLError {
constructor(groupId: number) {
super(`Group '${groupId}' not found!`);
}
}

@ -0,0 +1,65 @@
import * as sqz from "sequelize";
import {
BelongsTo,
BelongsToMany,
Column,
CreatedAt, ForeignKey,
HasMany,
Model,
NotNull,
Table,
Unique,
UpdatedAt,
} from "sequelize-typescript";
import {ChatMessage} from "./ChatMessage";
import {ChatRoom} from "./ChatRoom";
import {GroupAdmin} from "./GroupAdmin";
import {GroupMember} from "./GroupMember";
import {User} from "./User";
@Table({underscored: true})
export class Group extends Model<Group> {
@NotNull
@Column( {allowNull: false})
public name: string;
@NotNull
@ForeignKey(() => User)
@Column({allowNull: false})
public creatorId: number;
@NotNull
@ForeignKey(() => ChatRoom)
@Column({allowNull: false})
public chatId: number;
@BelongsTo(() => User, "creatorId")
public rCreator: User;
@BelongsToMany(() => User, () => GroupAdmin)
public rAdmins: User[];
@BelongsToMany(() => User, () => GroupMember)
public rMembers: User[];
@BelongsTo(() => ChatRoom)
public rChat: ChatRoom;
public async creator(): Promise<User> {
return await this.$get("rCreator") as User;
}
public async admins(): Promise<User[]> {
return await this.$get("rAdmins") as User[];
}
public async members({first, offset}: {first: number, offset: number}): Promise<User[]> {
const limit = first || 10;
offset = offset || 0;
return await this.$get("rMembers", {limit, offset}) as User[];
}
public async chat(): Promise<ChatRoom> {
return await this.$get("rChat") as ChatRoom;
}
}

@ -0,0 +1,28 @@
import * as sqz from "sequelize";
import {
BelongsTo,
BelongsToMany,
Column,
CreatedAt, ForeignKey,
HasMany,
Model, Not,
NotNull,
Table,
Unique,
UpdatedAt,
} from "sequelize-typescript";
import {Group} from "./Group";
import {User} from "./User";
@Table({underscored: true})
export class GroupAdmin extends Model<GroupAdmin> {
@NotNull
@ForeignKey(() => User)
@Column({allowNull: false})
public userId: number;
@NotNull
@ForeignKey(() => Group)
@Column({allowNull: false})
public groupId: number;
}

@ -0,0 +1,28 @@
import * as sqz from "sequelize";
import {
BelongsTo,
BelongsToMany,
Column,
CreatedAt, ForeignKey,
HasMany,
Model, Not,
NotNull,
Table,
Unique,
UpdatedAt,
} from "sequelize-typescript";
import {Group} from "./Group";
import {User} from "./User";
@Table({underscored: true})
export class GroupMember extends Model<GroupMember> {
@NotNull
@ForeignKey(() => User)
@Column({allowNull: false})
public userId: number;
@NotNull
@ForeignKey(() => Group)
@Column({allowNull: false})
public groupId: number;
}

@ -15,6 +15,9 @@ import {ChatMember} from "./ChatMember";
import {ChatMessage} from "./ChatMessage"; import {ChatMessage} from "./ChatMessage";
import {ChatRoom} from "./ChatRoom"; import {ChatRoom} from "./ChatRoom";
import {Friendship} from "./Friendship"; import {Friendship} from "./Friendship";
import {Group} from "./Group";
import {GroupAdmin} from "./GroupAdmin";
import {GroupMember} from "./GroupMember";
import {Post} from "./Post"; import {Post} from "./Post";
import {PostVote} from "./PostVote"; import {PostVote} from "./PostVote";
import {Request, RequestType} from "./Request"; import {Request, RequestType} from "./Request";
@ -52,6 +55,12 @@ export class User extends Model<User> {
@BelongsToMany(() => ChatRoom, () => ChatMember) @BelongsToMany(() => ChatRoom, () => ChatMember)
public rChats: ChatRoom[]; public rChats: ChatRoom[];
@BelongsToMany(() => Group, () => GroupAdmin)
public rAdministratedGroups: Group[];
@BelongsToMany(() => Group, () => GroupMember)
public rGroups: Group[];
@HasMany(() => Post, "authorId") @HasMany(() => Post, "authorId")
public rPosts: Post[]; public rPosts: Post[];
@ -64,6 +73,9 @@ export class User extends Model<User> {
@HasMany(() => ChatMessage, "authorId") @HasMany(() => ChatMessage, "authorId")
public messages: ChatMessage[]; public messages: ChatMessage[];
@HasMany(() => Group, "creatorId")
public rCreatedGroups: Group[];
@CreatedAt @CreatedAt
public readonly createdAt!: Date; public readonly createdAt!: Date;
@ -102,6 +114,18 @@ export class User extends Model<User> {
return this.$count("rPosts"); return this.$count("rPosts");
} }
public async administratedGroups(): Promise<Group[]> {
return await this.$get("rAdministratedGroups") as Group[];
}
public async createdGroups(): Promise<Group[]> {
return await this.$get("rCreatedGroups") as Group[];
}
public async groups(): Promise<Group[]> {
return await this.$get("rGroups") as Group[];
}
public async denyRequest(sender: number, type: RequestType) { public async denyRequest(sender: number, type: RequestType) {
const request = await this.$get("rReceivedRequests", const request = await this.$get("rReceivedRequests",
{where: {senderId: sender, requestType: type}}) as Request[]; {where: {senderId: sender, requestType: type}}) as Request[];

@ -6,3 +6,6 @@ export {Post} from "./Post";
export {PostVote} from "./PostVote"; export {PostVote} from "./PostVote";
export {Request} from "./Request"; export {Request} from "./Request";
export {User} from "./User"; export {User} from "./User";
export {Group} from "./Group";
export {GroupAdmin} from "./GroupAdmin";
export {GroupMember} from "./GroupMember";

Loading…
Cancel
Save