diff --git a/package-lock.json b/package-lock.json index c82d4b5..3689e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -159,6 +159,12 @@ "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", "dev": true }, + "@types/http-status": { + "version": "0.2.30", + "resolved": "https://registry.npmjs.org/@types/http-status/-/http-status-0.2.30.tgz", + "integrity": "sha512-wcBc5XEOMmhuoWfNhwnpw8+tVAsueUeARxCTcRQ0BCN5V/dyKQBJNWdxmvcZW5IJWoeU47UWQ+ACCg48KKnqyA==", + "dev": true + }, "@types/js-yaml": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", @@ -3664,6 +3670,11 @@ "sshpk": "^1.7.0" } }, + "http-status": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.3.2.tgz", + "integrity": "sha512-vR1YTaDyi2BukI0UiH01xy92oiZi4in7r0dmSPnrZg72Vu1SzyOLalwWP5NUk1rNiB2L+XVK2lcSVOqaertX8A==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index 664e5a3..7dcee4a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/express-socket.io-session": "^1.3.2", "@types/fs-extra": "^8.0.0", "@types/graphql": "^14.2.3", + "@types/http-status": "^0.2.30", "@types/js-yaml": "^3.12.1", "@types/node": "^12.7.2", "@types/pg": "^7.11.0", @@ -54,6 +55,7 @@ "g": "^2.0.1", "graphql": "^14.4.2", "graphql-import": "^0.7.1", + "http-status": "^1.3.2", "js-yaml": "^3.13.1", "pg": "^7.12.1", "pug": "^2.0.4", diff --git a/src/app.ts b/src/app.ts index 3a60de6..7ea9b9b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,6 +14,8 @@ import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; +const logger = globals.logger; + const PgSession = connectPgSimple(session); class App { @@ -40,7 +42,7 @@ class App { secure: "auto", }, resave: false, - saveUninitialized: true, // TODO: Set to false and only save when accepted by user. + saveUninitialized: false, secret: globals.config.session.secret, store: new PgSession({ pool: dataaccess.pool, @@ -60,14 +62,18 @@ class App { this.app.use(express.static(path.join(__dirname, "public"))); this.app.use(cookieParser()); this.app.use(appSession); + this.app.use((req, res, next) => { + logger.verbose(`${req.method} ${req.url}`); + next(); + }); this.app.use(routes.router); - this.app.use("/graphql", graphqlHTTP(async (request, response) => { + this.app.use("/graphql", graphqlHTTP((request, response) => { return { // @ts-ignore all context: {session: request.session}, graphiql: true, - rootValue: await routes.resolvers(request, response), - schema: buildSchema(importSchema("./public/graphql/schema.graphql")), + rootValue: routes.resolvers(request, response), + schema: buildSchema(importSchema(path.join(__dirname, "./public/graphql/schema.graphql"))), }; })); } @@ -77,11 +83,11 @@ class App { */ public start() { if (globals.config.server.port) { - globals.logger.info(`Starting server...`); + logger.info(`Starting server...`); this.app.listen(globals.config.server.port); - globals.logger.info(`Server running on port ${globals.config.server.port}`); + logger.info(`Server running on port ${globals.config.server.port}`); } else { - globals.logger.error("No port specified in the config." + + logger.error("No port specified in the config." + "Please configure a port in the config.yaml."); } } diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 5696547..6a9c854 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -50,7 +50,7 @@ namespace dataaccess { * @param userHandle */ export async function getUserByHandle(userHandle: string) { - const result = await this.queryHelper.first({ + const result = await queryHelper.first({ text: "SELECT * FROM users WHERE users.handle = $1", values: [userHandle], }); @@ -62,12 +62,16 @@ namespace dataaccess { * @param email * @param password */ - export async function getUserByLogin(email: string, password: string) { - const result = await this.queryHelper.first({ + export async function getUserByLogin(email: string, password: string): Promise { + const result = await queryHelper.first({ text: "SELECT * FROM users WHERE email = $1 AND password = $2", values: [email, password], }); - return new Profile(result.id, result); + if (result) { + return new Profile(result.id, result); + } else { + return null; + } } /** @@ -77,7 +81,7 @@ namespace dataaccess { * @param password */ export async function registerUser(username: string, email: string, password: string) { - const result = await this.queryHelper.first({ + const result = await queryHelper.first({ text: "INSERT INTO users (name, handle, password, email) VALUES ($1, $2, $3, $4) RETURNING *", values: [username, generateHandle(username), password, email], }); diff --git a/src/lib/globals.ts b/src/lib/globals.ts index a85ffea..3457fa5 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -15,6 +15,10 @@ const defaultConfig = __dirname + "/../default-config.yaml"; // ensure that the config exists by copying the default config. if (!(fsx.pathExistsSync(configPath))) { fsx.copySync(defaultConfig, configPath); +} else { + const conf = yaml.safeLoad(fsx.readFileSync(configPath, "utf-8")); + const defConf = yaml.safeLoad(fsx.readFileSync(defaultConfig, "utf-8")); + fsx.writeFileSync(configPath, yaml.safeDump(Object.assign(defConf, conf))); } /** @@ -28,10 +32,11 @@ namespace globals { format: winston.format.combine( winston.format.timestamp(), winston.format.colorize(), - winston.format.printf(({ level, message, label, timestamp }) => { + winston.format.printf(({ level, message, timestamp }) => { return `${timestamp} ${level}: ${message}`; }), ), + level: "debug", }), ], }); diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index c5b03c3..ea9c4eb 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -22,6 +22,12 @@ type Mutation { "Accepts the usage of cookies." acceptCookies: Boolean + "Login of the user. The passwordHash should be a sha512 hash of the password." + login(email: String, passwordHash: String): User + + "Logout of the user." + logout: Boolean + "Upvote/downvote a Post" vote(postId: ID!, type: [VoteType!]!): Boolean diff --git a/src/routes/home.ts b/src/routes/home.ts index 13bb254..2af5947 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,5 +1,9 @@ import {Router} from "express"; +import {GraphQLError} from "graphql"; +import * as status from "http-status"; +import {constants} from "http2"; import {Server} from "socket.io"; +import dataaccess from "../lib/dataaccess"; import Route from "../lib/Route"; /** @@ -36,9 +40,36 @@ class HomeRoute extends Route { * @param req - the request object * @param res - the response object */ - public async resolver(req: any, res: any): Promise { + public resolver(req: any, res: any): any { return { - // TODO: Define grapql resolvers + acceptCookies() { + req.session.cookiesAccepted = true; + return true; + }, + async login(args: any) { + if (args.email && args.passwordHash) { + const user = await dataaccess.getUserByLogin(args.email, args.passwordHash); + if (user && user.id) { + req.session.userId = user.id; + return user; + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("Invalid login data."); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No email or password given."); + } + }, + logout() { + if (req.session.user) { + delete req.session.user; + return true; + } else { + res.status(status.UNAUTHORIZED); + return new GraphQLError("User is not logged in."); + } + }, }; } diff --git a/src/routes/index.ts b/src/routes/index.ts index e512ee6..eb65750 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -28,7 +28,7 @@ namespace routes { * @param request * @param response */ - export async function resolvers(request: any, response: any): Promise { + export function resolvers(request: any, response: any): Promise { return homeRoute.resolver(request, response); }