Merge remote-tracking branch 'origin/julius-dev' into julius-dev

pull/5/head
Trivernis 5 years ago
commit 1debb27933

@ -54,6 +54,7 @@
"typescript": "^3.7.2"
},
"dependencies": {
"@types/uuid": "^3.4.6",
"compression": "^1.7.4",
"connect-session-sequelize": "^6.0.0",
"cookie-parser": "^1.4.4",
@ -77,6 +78,7 @@
"socket.io": "^2.2.0",
"socket.io-redis": "^5.2.0",
"sqlite3": "^4.1.0",
"uuid": "^3.3.3",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.2.1"
}

@ -80,6 +80,18 @@ class App {
if (globals.config.server?.cors) {
this.app.use(cors());
}
// handle authentification via bearer in the Authorization header
this.app.use(async (req, res, next) => {
if (!req.session.userId && req.headers.authorization) {
const bearer = req.headers.authorization.split("Bearer ")[1];
if (bearer) {
const user = await dataaccess.getUserByToken(bearer);
// @ts-ignore
req.session.userId = user.id;
}
}
next();
});
this.app.use((req, res, next) => {
logger.verbose(`${req.method} ${req.url}`);
next();

@ -113,6 +113,23 @@ export function resolver(req: any, res: any): any {
return new NotLoggedInGqlError();
}
},
async getToken({email, passwordHash}: {email: string, passwordHash: string}) {
if (email && passwordHash) {
try {
const user = await dataaccess.getUserByLogin(email, passwordHash);
return {
expires: Number(user.authExpire),
value: user.token(),
};
} catch (err) {
res.status(400);
return err.graphqlError;
}
} else {
res.status(400);
return new GraphQLError("No email or password specified.");
}
},
async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) {
if (username && email && passwordHash) {
if (!is.email(email)) {

@ -22,6 +22,9 @@ type Query {
"returns the post filtered by the sort type with pagination."
getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post]
"Returns an access token for the user that can be used in requests. To user the token in requests, it has to be set in the HTTP header 'Authorization' with the format Bearer <token>."
getToken(email: String!, passwordHash: String!): Token!
}
type Mutation {
@ -29,7 +32,7 @@ type Mutation {
acceptCookies: Boolean
"Login of the user. The passwordHash should be a sha512 hash of the password."
login(email: String, passwordHash: String): Profile
login(email: String!, passwordHash: String!): Profile
"Registers the user."
register(username: String, email: String, passwordHash: String): Profile
@ -371,6 +374,12 @@ type Event {
participants(first: Int=10, offset: Int=0): [User!]!
}
"respresents an access token entry with the value as the acutal token and expires as the date the token expires."
type Token {
value: String!
expires: String!
}
"represents the type of vote performed on a post"
enum VoteType {
UPVOTE

@ -93,6 +93,14 @@ namespace dataaccess {
}
}
/**
* Returns the user by auth token.
* @param token
*/
export async function getUserByToken(token: string): Promise<models.User> {
return models.User.findOne({where: {authToken: token}});
}
/**
* Registers a user with a username and password returning a user
* @param username

@ -24,22 +24,37 @@ export class Post extends Model<Post> {
@CreatedAt
public readonly createdAt!: Date;
/**
* Returns the author of a post
*/
public async author(): Promise<User> {
return await this.$get("rAuthor") as User;
}
/**
* Returns the votes on a post
*/
public async votes(): Promise<Array<User & {PostVote: PostVote}>> {
return await this.$get("rVotes") as Array<User & {PostVote: PostVote}>;
}
/**
* Returns the markdown-rendered html content of the post
*/
public get htmlContent() {
return markdown.render(this.getDataValue("content"));
}
/**
* Returns the number of upvotes on the post
*/
public async upvotes() {
return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.UPVOTE).length;
}
/**
* Returns the number of downvotes on the post
*/
public async downvotes() {
return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length;
}

@ -10,6 +10,7 @@ import {
Unique,
UpdatedAt,
} from "sequelize-typescript";
import * as uuidv4 from "uuid/v4";
import {RequestNotFoundError} from "../errors/RequestNotFoundError";
import {UserNotFoundError} from "../errors/UserNotFoundError";
import {ChatMember} from "./ChatMember";
@ -53,6 +54,13 @@ export class User extends Model<User> {
@Column({defaultValue: {}, allowNull: false, type: sqz.JSON})
public frontendSettings: any;
@Unique
@Column({defaultValue: uuidv4, unique: true})
public authToken: string;
@Column({defaultValue: () => Date.now() + 7200000})
public authExpire: Date;
@BelongsToMany(() => User, () => Friendship, "userId")
public rFriends: User[];
@ -130,6 +138,18 @@ export class User extends Model<User> {
return JSON.stringify(this.getDataValue("frontendSettings"));
}
/**
* Returns the token for the user that can be used as a bearer in requests
*/
public async token(): Promise<string> {
if (this.getDataValue("authExpire") < new Date(Date.now())) {
this.authToken = null;
this.authExpire = null;
await this.save();
}
return this.getDataValue("authToken");
}
/**
* All friends of the user
* @param first

@ -232,6 +232,13 @@
dependencies:
"@types/node" "*"
"@types/uuid@^3.4.6":
version "3.4.6"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.6.tgz#d2c4c48eb85a757bf2927f75f939942d521e3016"
integrity sha512-cCdlC/1kGEZdEglzOieLDYBxHsvEOIg7kp/2FYyVR9Pxakq+Qf/inL3RKQ+PA8gOlI/NnL+fXmQH12nwcGzsHw==
dependencies:
"@types/node" "*"
"@types/validator@*", "@types/validator@^10.11.3":
version "10.11.3"
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-10.11.3.tgz#945799bef24a953c5bc02011ca8ad79331a3ef25"

Loading…
Cancel
Save