Browse Source

Client side authentication now being handled

master
David Ludwig 4 years ago
parent
commit
16405d84ae
7 changed files with 113 additions and 13 deletions
  1. +7
    -1
      src/app/App.vue
  2. +57
    -5
      src/app/auth.ts
  3. +14
    -0
      src/app/routes/index.ts
  4. +6
    -0
      src/app/views/Error404.vue
  5. +4
    -3
      src/app/views/Login.vue
  6. +22
    -0
      src/server/database/entities/User.ts
  7. +3
    -4
      src/server/services/WebServer/routes/auth.ts

+ 7
- 1
src/app/App.vue View File

@ -6,5 +6,11 @@
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({});
import * as auth from "./auth";
export default defineComponent({
setup() {
auth.loadToken();
}
});
</script>

+ 57
- 5
src/app/auth.ts View File

@ -1,19 +1,71 @@
import jwtDecode from "jwt-decode";
let token: string | null;
/**
* The active JWT
*/
let token: string | null;
/**
* The decoded user object
*/
let user: {
id: number,
name: string,
isAdmin: boolean
} | null;
/**
* Check if the user is an admin
*/
export function isAdmin() {
return user && user.isAdmin;
}
/**
* Check if the client is authenticated
*/
export function isAuthenticated() {
return Boolean(token);
}
export function decodeToken(token: string) {
return jwtDecode(token);
/**
* Load the token from local storage
*/
export function loadToken() {
try {
token = localStorage.getItem("jwt");
user = jwtDecode(<string>token);
} catch(e) {
console.log("Failed to load token");
token = null;
user = null;
return false;
}
return true;
}
/**
* Delete the token from local storage
*/
export function forgetToken() {
token = null;
user = null;
localStorage.removeItem("jwt");
}
export function storeToken(token: string) {
localStorage.setItem("jwt", token);
/**
* Store a JWT token in local storage
*/
export function storeToken(jwtToken: string) {
try {
user = jwtDecode(jwtToken);
token = jwtToken;
localStorage.setItem("jwt", jwtToken);
} catch(e) {
user = null;
token = null;
return false;
}
return true;
}

+ 14
- 0
src/app/routes/index.ts View File

@ -41,6 +41,20 @@ const routes: RouteRecordRaw[] = [
name: "Register",
component: () => import("../views/Register.vue"),
beforeEnter: requiresGuest
},
{
path: "/logout",
name: "Logout",
component: {
beforeRouteEnter(to, from, next) {
auth.forgetToken();
next({ name: "Login" });
}
}
},
{
path: "/:pathMatch(.*)*",
component: () => import("../views/Error404.vue")
}
];


+ 6
- 0
src/app/views/Error404.vue View File

@ -0,0 +1,6 @@
<template>
<div class="mx-auto my-auto">
<h1 class="text-9xl font-black opacity-30">404</h1>
</div>
</template>

+ 4
- 3
src/app/views/Login.vue View File

@ -59,8 +59,10 @@ export default defineComponent({
.then(async response => {
this.isSubmitting = false;
let body = await response.json();
console.log("The response is:", response.status);
if (response.status !== 200) {
if (response.status === 401) {
body.errors = { "email": [ "Email or password is incorrect" ] };
}
if (body.errors) {
for (let fieldName in this.fields) {
let field = <any>this.$refs[fieldName];
@ -70,9 +72,8 @@ export default defineComponent({
}
return;
}
console.log("Successful login", body.token);
auth.storeToken(body.token);
this.$router.push({ name: "Login" });
this.$router.push({ name: "Home" });
})
.catch(e => {
console.error("Error occurred during submission:", e);


+ 22
- 0
src/server/database/entities/User.ts View File

@ -1,5 +1,8 @@
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany } from "typeorm";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { MovieTicket } from "./MovieTicket";
import Application from "@server/Application";
@Entity()
export class User extends BaseEntity
@ -21,4 +24,23 @@ export class User extends BaseEntity
@OneToMany(() => User, user => user.movieTickets)
movieTickets!: MovieTicket[];
/**
* Authenticate a user and return an auth token upon success
*/
public static async authenticate(email: string, password: string) {
let user = <User>await User.findOne({ email });
if (user === undefined || !(await bcrypt.compare(password, user.password))) {
return undefined;
}
return user.createJwtToken(Application.instance().APP_KEY);
}
/**
* Create an auth token for the user
*/
public createJwtToken(key: string, expiresIn: number = 60*60*24) {
let body = { id: this.id, name: this.name, isAdmin: this.isAdmin };
return jwt.sign(body, key, { expiresIn });
}
}

+ 3
- 4
src/server/services/WebServer/routes/auth.ts View File

@ -23,20 +23,19 @@ export default function register(factory: RouteRegisterFactory, app: Application
factory.post("/auth/login", handle([LoginRequest], async (request, reply) => {
let body = <ILoginFormBody>request.body;
let user = await User.findOne({ email: body.email });
if (user === undefined || !(await bcrypt.compare(body.password, user.password))) {
let token = await User.authenticate(body.email, body.password);
if (token === undefined) {
reply.status(401);
reply.send({ "status": "unauthorized" });
return
}
let token = jwt.sign({ id: (<User>user).id }, app.APP_KEY, { expiresIn: 60*60*24 });
// Store the header/payload in the client, store the signature in a secure httpOnly cookie
let [header, payload, signature] = token.split('.');
reply.setCookie("jwt_signature", signature, {
httpOnly: true,
sameSite: true,
secure: true
});
console.log(signature);
reply.send({ status: "success", token: `${header}.${payload}` });
}));


Loading…
Cancel
Save