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 * as status from "http-status";
import dataaccess from "../lib/dataaccess";
import {UserNotFoundError} from "../lib/errors/UserNotFoundError";
import {Group} 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 {InternalEvents} from "../lib/InternalEvents";
import {is} from "../lib/regex";
@ -106,7 +108,7 @@ export function resolver(req: any, res: any): any {
if (post) {
return await post.vote(req.session.userId, type);
} else {
res.status(400);
res.status(status.BAD_REQUEST);
return new PostNotFoundGqlError(postId);
}
} 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}) {
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)"
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 {
@ -148,6 +163,16 @@ type Profile implements UserData {
"all received request for groupChats/friends/events"
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"
@ -225,6 +250,26 @@ type ChatMessage {
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"
enum VoteType {
UPVOTE

@ -1,3 +1,4 @@
import * as crypto from "crypto";
import {Sequelize} from "sequelize-typescript";
import {ChatNotFoundError} from "./errors/ChatNotFoundError";
import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError";
@ -36,6 +37,9 @@ namespace dataaccess {
models.PostVote,
models.Request,
models.User,
models.Group,
models.GroupAdmin,
models.GroupMember,
]);
} catch (err) {
globals.logger.error(err.message);
@ -62,6 +66,9 @@ namespace dataaccess {
* @param password
*/
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}});
if (user) {
return user;
@ -77,6 +84,9 @@ namespace dataaccess {
* @param password
*/
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 handle = generateHandle(username);
if (!existResult) {
@ -201,6 +211,27 @@ namespace dataaccess {
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.
*/

@ -11,3 +11,9 @@ export class PostNotFoundGqlError extends GraphQLError {
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 {ChatRoom} from "./ChatRoom";
import {Friendship} from "./Friendship";
import {Group} from "./Group";
import {GroupAdmin} from "./GroupAdmin";
import {GroupMember} from "./GroupMember";
import {Post} from "./Post";
import {PostVote} from "./PostVote";
import {Request, RequestType} from "./Request";
@ -52,6 +55,12 @@ export class User extends Model<User> {
@BelongsToMany(() => ChatRoom, () => ChatMember)
public rChats: ChatRoom[];
@BelongsToMany(() => Group, () => GroupAdmin)
public rAdministratedGroups: Group[];
@BelongsToMany(() => Group, () => GroupMember)
public rGroups: Group[];
@HasMany(() => Post, "authorId")
public rPosts: Post[];
@ -64,6 +73,9 @@ export class User extends Model<User> {
@HasMany(() => ChatMessage, "authorId")
public messages: ChatMessage[];
@HasMany(() => Group, "creatorId")
public rCreatedGroups: Group[];
@CreatedAt
public readonly createdAt!: Date;
@ -102,6 +114,18 @@ export class User extends Model<User> {
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) {
const request = await this.$get("rReceivedRequests",
{where: {senderId: sender, requestType: type}}) as Request[];

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

Loading…
Cancel
Save