More authentication
This commit is contained in:
parent
78c35b2ac3
commit
409b8af6a3
1
sql/users/details.sql
Normal file
1
sql/users/details.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT email FROM users WHERE id=$1;
|
13
src/api.ts
13
src/api.ts
@ -14,7 +14,7 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { db } from "@kredens/db";
|
import { db } from "@kredens/db";
|
||||||
import { ApolloServer, gql } from "apollo-server-express";
|
import { ApolloServer, AuthenticationError, gql } from "apollo-server-express";
|
||||||
import { Kind } from "graphql/language";
|
import { Kind } from "graphql/language";
|
||||||
import { GraphQLScalarType, GraphQLScalarTypeConfig } from "graphql/type";
|
import { GraphQLScalarType, GraphQLScalarTypeConfig } from "graphql/type";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
@ -67,6 +67,17 @@ const resolvers = {
|
|||||||
|
|
||||||
export function server() {
|
export function server() {
|
||||||
return new ApolloServer({
|
return new ApolloServer({
|
||||||
|
context: async req => {
|
||||||
|
const user = req.req.user;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new AuthenticationError("you must be logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user
|
||||||
|
};
|
||||||
|
},
|
||||||
resolvers,
|
resolvers,
|
||||||
typeDefs
|
typeDefs
|
||||||
});
|
});
|
||||||
|
40
src/auth.ts
Normal file
40
src/auth.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { db } from "@kredens/db";
|
||||||
|
import { User } from "@kredens/db/models";
|
||||||
|
import express from "express";
|
||||||
|
import { None } from "monet";
|
||||||
|
|
||||||
|
export const getUser = async (req: express.Request) =>
|
||||||
|
req.session.userID ? db.users.details(req.session.userID) : None<User>();
|
||||||
|
|
||||||
|
export const authMiddleware: () => express.Handler = () => async (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
next
|
||||||
|
) => {
|
||||||
|
if (req.session.userID) {
|
||||||
|
const user = await getUser(req);
|
||||||
|
|
||||||
|
if (user.isSome()) {
|
||||||
|
req.user = user.some();
|
||||||
|
} else {
|
||||||
|
delete req.session.userID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
24
src/custom.d.ts
vendored
Normal file
24
src/custom.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { User } from "@kredens/db/models";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface Request {
|
||||||
|
user?: User;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ import {
|
|||||||
MigrationRepository,
|
MigrationRepository,
|
||||||
UserRepository
|
UserRepository
|
||||||
} from "@kredens/db/repos";
|
} from "@kredens/db/repos";
|
||||||
import monitor from "pg-monitor";
|
|
||||||
import pgPromise, { IDatabase, IInitOptions } from "pg-promise";
|
import pgPromise, { IDatabase, IInitOptions } from "pg-promise";
|
||||||
|
|
||||||
type ExtendedProtocol = IDatabase<Extensions> & Extensions;
|
type ExtendedProtocol = IDatabase<Extensions> & Extensions;
|
||||||
@ -31,6 +31,11 @@ const initOptions: IInitOptions<Extensions> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const pgp: pgPromise.IMain = pgPromise(initOptions);
|
const pgp: pgPromise.IMain = pgPromise(initOptions);
|
||||||
monitor.attach(initOptions);
|
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
// tslint:disable-next-line:no-implicit-dependencies
|
||||||
|
import("pg-monitor").then(monitor => monitor.attach(initOptions));
|
||||||
|
}
|
||||||
|
|
||||||
const db: ExtendedProtocol = pgp(process.env.PG_CONNECTION_STRING);
|
const db: ExtendedProtocol = pgp(process.env.PG_CONNECTION_STRING);
|
||||||
export { db, pgp };
|
export { db, pgp };
|
||||||
|
@ -20,3 +20,8 @@ export interface Migration {
|
|||||||
name: string;
|
name: string;
|
||||||
applied_at: DateTime;
|
applied_at: DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ import { users as sql } from "@kredens/db/sql";
|
|||||||
import argon2 from "argon2";
|
import argon2 from "argon2";
|
||||||
import { Maybe, None, Some } from "monet";
|
import { Maybe, None, Some } from "monet";
|
||||||
import { IDatabase, IMain } from "pg-promise";
|
import { IDatabase, IMain } from "pg-promise";
|
||||||
|
import { User } from "@kredens/db/models";
|
||||||
|
|
||||||
export class UserRepository {
|
export class UserRepository {
|
||||||
private db: IDatabase<any>;
|
private db: IDatabase<any>;
|
||||||
@ -47,4 +48,12 @@ export class UserRepository {
|
|||||||
.one(sql.create, [email, encryptedPassword])
|
.one(sql.create, [email, encryptedPassword])
|
||||||
.then((user: { id: number }) => +user.id);
|
.then((user: { id: number }) => +user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async details(id: number): Promise<Maybe<User>> {
|
||||||
|
return this.db
|
||||||
|
.oneOrNone(sql.details, [id])
|
||||||
|
.then((user: { email: string }) =>
|
||||||
|
user !== null ? Some({ ...user, id }) : None()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ const migrations = {
|
|||||||
|
|
||||||
const users = {
|
const users = {
|
||||||
create: sql("users/create.sql"),
|
create: sql("users/create.sql"),
|
||||||
|
details: sql("users/details.sql"),
|
||||||
login: sql("users/login.sql")
|
login: sql("users/login.sql")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
||||||
|
15
src/frontend/vue-shim.d.ts
vendored
15
src/frontend/vue-shim.d.ts
vendored
@ -1,3 +1,18 @@
|
|||||||
|
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
declare module "*.vue" {
|
declare module "*.vue" {
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
export default Vue;
|
export default Vue;
|
||||||
|
51
src/index.ts
51
src/index.ts
@ -14,6 +14,7 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { server as graphqlServer } from "@kredens/api";
|
import { server as graphqlServer } from "@kredens/api";
|
||||||
|
import { authMiddleware } from "@kredens/auth";
|
||||||
import { db } from "@kredens/db";
|
import { db } from "@kredens/db";
|
||||||
import logger from "@kredens/logger";
|
import logger from "@kredens/logger";
|
||||||
import indexRouter from "@kredens/routes/";
|
import indexRouter from "@kredens/routes/";
|
||||||
@ -25,18 +26,33 @@ import pinoExpress from "express-pino-logger";
|
|||||||
import session, { SessionOptions } from "express-session";
|
import session, { SessionOptions } from "express-session";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await db.tx(async t => {
|
await db.tx(async t => {
|
||||||
await t.migrations.create();
|
await t.migrations.create();
|
||||||
await t.migrations.apply();
|
await t.migrations.apply();
|
||||||
});
|
});
|
||||||
|
|
||||||
const server = graphqlServer();
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const expressPino = pinoExpress({ logger });
|
const expressPino = pinoExpress({ logger });
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
app.use(expressPino);
|
app.use(expressPino);
|
||||||
|
|
||||||
|
if (app.settings.env === "development") {
|
||||||
|
const webpack = await import("webpack").then(p => p.default); // tslint:disable-line:no-implicit-dependencies
|
||||||
|
// tslint:disable-next-line:no-implicit-dependencies
|
||||||
|
const webpackDevMiddleware = await import("webpack-dev-middleware").then(
|
||||||
|
p => p.default
|
||||||
|
);
|
||||||
|
const config = await import("../webpack.config").then(p => p.default);
|
||||||
|
|
||||||
|
const compiler = webpack(config);
|
||||||
|
app.use(
|
||||||
|
webpackDevMiddleware(compiler, {
|
||||||
|
publicPath: "/assets/"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
@ -52,25 +68,26 @@ async function main() {
|
|||||||
}
|
}
|
||||||
app.use(session(sessionOptions));
|
app.use(session(sessionOptions));
|
||||||
app.use("/bootstrap", bootstrapRouter);
|
app.use("/bootstrap", bootstrapRouter);
|
||||||
|
|
||||||
|
app.use(authMiddleware());
|
||||||
|
const apiServer = graphqlServer();
|
||||||
|
apiServer.applyMiddleware({
|
||||||
|
app,
|
||||||
|
path: "/graphql"
|
||||||
|
});
|
||||||
|
|
||||||
app.use(csrf());
|
app.use(csrf());
|
||||||
|
|
||||||
if (app.settings.env === "development") {
|
|
||||||
const webpack = require("webpack"); // tslint:disable-line:no-implicit-dependencies
|
|
||||||
const webpackDevMiddleware = require("webpack-dev-middleware"); // tslint:disable-line:no-implicit-dependencies
|
|
||||||
const config = require("../webpack.config").default;
|
|
||||||
|
|
||||||
const compiler = webpack(config);
|
|
||||||
app.use(
|
|
||||||
webpackDevMiddleware(compiler, {
|
|
||||||
publicPath: "/assets/"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.set("view engine", "pug");
|
app.set("view engine", "pug");
|
||||||
|
|
||||||
|
app.use(async (req, res, next) => {
|
||||||
|
res.locals.csrfToken = req.csrfToken();
|
||||||
|
res.locals.user = req.user;
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
app.use("/", indexRouter);
|
app.use("/", indexRouter);
|
||||||
server.applyMiddleware({ app, path: "/graphql" });
|
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
next(createHttpError(404));
|
next(createHttpError(404));
|
||||||
@ -79,7 +96,7 @@ async function main() {
|
|||||||
const port = 3000;
|
const port = 3000;
|
||||||
app.listen(port, () =>
|
app.listen(port, () =>
|
||||||
logger.info("Example app listening", {
|
logger.info("Example app listening", {
|
||||||
uri: `http://localhost:${port}${server.graphqlPath}`
|
uri: `http://localhost:${port}${apiServer.graphqlPath}`
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,16 @@ import express from "express";
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", async (req, res, next) => {
|
router.get("/", async (req, res, next) => {
|
||||||
res.render("login", {
|
res.render("login");
|
||||||
csrfToken: req.csrfToken()
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/", async (req, res, next) => {
|
router.post("/", async (req, res, next) => {
|
||||||
const userID = await db.users.login(req.body.email, req.body.password);
|
const userID = await db.users.login(req.body.email, req.body.password);
|
||||||
if (userID.isSome()) {
|
if (userID.isSome()) {
|
||||||
res.send(`Hi, ${userID.some()}`);
|
req.session.userID = userID.some();
|
||||||
|
res.redirect("/");
|
||||||
} else {
|
} else {
|
||||||
res.send(`Go away.`);
|
res.redirect("/auth/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { box, unbox } from "@kredens/crypto";
|
import { box, unbox } from "@kredens/crypto";
|
||||||
import { db } from "@kredens/db";
|
import { db } from "@kredens/db";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import authRouter from "./auth";
|
import authRouter from "./auth";
|
||||||
import homeRouter from "./home";
|
import homeRouter from "./home";
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*"
|
"./src/**/*"
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
html
|
extends layout.pug
|
||||||
head
|
|
||||||
title= title
|
block content
|
||||||
body
|
if user
|
||||||
h1= message
|
p.auth Hi, #{ user.email }
|
||||||
|
else
|
||||||
|
p.auth
|
||||||
|
| Who are you?
|
||||||
|
|
|
||||||
|
a(href="/auth") Login you coward.
|
||||||
div#body
|
div#body
|
||||||
p I am a placeholder
|
p I am a placeholder
|
||||||
|
|
||||||
|
block scripts
|
||||||
script(src="/assets/main.bundle.js")
|
script(src="/assets/main.bundle.js")
|
@ -3,3 +3,4 @@ html
|
|||||||
title Kredens - #{title}
|
title Kredens - #{title}
|
||||||
body
|
body
|
||||||
block content
|
block content
|
||||||
|
block scripts
|
Loading…
x
Reference in New Issue
Block a user