basic login boilerplatre
This commit is contained in:
parent
674f1e0443
commit
63c02f62aa
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
dist
|
||||
static
|
||||
node_modules
|
||||
.parcel-cache
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120
|
||||
}
|
@ -1,39 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { setConstantValue } from 'typescript';
|
||||
|
||||
const App = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const submit = () => {
|
||||
fetch('/sign_in', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
};
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Router } from './components/router';
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button type="button" onClick={submit}>Login</button>
|
||||
</>
|
||||
<BrowserRouter>
|
||||
<Router />
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
3
client/components/home/_home.jsx
Normal file
3
client/components/home/_home.jsx
Normal file
@ -0,0 +1,3 @@
|
||||
export const Home = () => {
|
||||
return <div>I am the home page</div>;
|
||||
};
|
10
client/components/router.jsx
Normal file
10
client/components/router.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { Home } from './home/_home';
|
||||
|
||||
export const Router = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
3
client/components/sign_in/_sign_in.jsx
Normal file
3
client/components/sign_in/_sign_in.jsx
Normal file
@ -0,0 +1,3 @@
|
||||
export const SignIn = () => {
|
||||
return <div>I am the sign in page</div>;
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './app';
|
||||
import { App } from './app';
|
||||
|
||||
const app = document.getElementById('app');
|
||||
ReactDOM.render(<App />, app);
|
||||
|
@ -8,6 +8,7 @@
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "6"
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,20 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/runtime@^7.7.6":
|
||||
version "7.16.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
|
||||
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
history@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-5.1.0.tgz#2e93c09c064194d38d52ed62afd0afc9d9b01ece"
|
||||
integrity sha512-zPuQgPacm2vH2xdORvGGz1wQMuHSIB56yNAy5FnLuwOwgSYyPKptJtcMm6Ev+hRGeS+GzhbmRacHzvlESbFwDg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.6"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@ -28,6 +42,21 @@ react-dom@^17.0.2:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-router-dom@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.0.2.tgz#860cefa697b9d4965eced3f91e82cdbc5995f3ad"
|
||||
integrity sha512-cOpJ4B6raFutr0EG8O/M2fEoyQmwvZWomf1c6W2YXBZuFBx8oTk/zqjXghwScyhfrtnt0lANXV2182NQblRxFA==
|
||||
dependencies:
|
||||
history "^5.1.0"
|
||||
react-router "6.0.2"
|
||||
|
||||
react-router@6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.0.2.tgz#bd2b0fa84fd1d152671e9f654d9c0b1f5a7c86da"
|
||||
integrity sha512-8/Wm3Ed8t7TuedXjAvV39+c8j0vwrI5qVsYqjFr5WkJjsJpEvNSoLRUbtqSEYzqaTUj1IV+sbPJxvO+accvU0Q==
|
||||
dependencies:
|
||||
history "^5.1.0"
|
||||
|
||||
react@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||
@ -36,6 +65,11 @@ react@^17.0.2:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
||||
scheduler@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
|
||||
|
@ -6,7 +6,7 @@
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/database/cli_config.ts",
|
||||
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config server/database/cli_config.ts",
|
||||
"db:start": "pg_ctl status || pg_ctl start",
|
||||
"db:stop": "pg_ctl status && pg_ctl stop",
|
||||
"db:migration:create": "cd server/migrations && typeorm migration:create -n ",
|
||||
@ -39,7 +39,6 @@
|
||||
"dotenv": "^10.0.0",
|
||||
"hbs": "^4.1.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"morgan": "^1.10.0",
|
||||
"pg": "^8.7.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
|
@ -1,7 +1,17 @@
|
||||
import { Body, Controller, Post, Res } from '@nestjs/common';
|
||||
import { UsersService } from 'server/providers/services/users.service';
|
||||
import { SignInDto } from '../dto/sign_in.dto';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { UsersService } from 'server/providers/services/users.service';
|
||||
import { SignInDto } from 'server/dto/sign_in.dto';
|
||||
|
||||
|
||||
// this is kind of a misnomer because we are doing token based auth
|
||||
// instead of session based auth
|
||||
@Controller()
|
||||
@ -9,19 +19,30 @@ export class SessionsController {
|
||||
constructor(private usersService: UsersService) {}
|
||||
|
||||
@Post('/sign_in')
|
||||
async signIn(@Body() body: SignInDto, @Res() res: Response) {
|
||||
console.log("DO I GET RAN?")
|
||||
const verified = await this.usersService.verify(
|
||||
async signIn(
|
||||
@Body() body: SignInDto,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
const { verified, user } = await this.usersService.verify(
|
||||
body.username,
|
||||
body.password,
|
||||
);
|
||||
|
||||
if (!verified) {
|
||||
res.status(400);
|
||||
console.log("here too??")
|
||||
res.json({ message: 'Invalid email or password' });
|
||||
return;
|
||||
throw new HttpException(
|
||||
'Invalid email or password.',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
res.json({ success: true });
|
||||
// Write JWT to cookie and send with response.
|
||||
const token = jwt.sign(
|
||||
{
|
||||
user_id: user.id,
|
||||
},
|
||||
process.env.ENCRYPTION_KEY,
|
||||
{ expiresIn: '1h' },
|
||||
);
|
||||
res.cookie('_token', token);
|
||||
return { token };
|
||||
}
|
||||
}
|
||||
|
50
server/controllers/users.controller.ts
Normal file
50
server/controllers/users.controller.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { Response } from 'express';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { CreateUserDto } from 'server/dto/create_user.dto';
|
||||
import { User } from 'server/entities/user.entity';
|
||||
import { UsersService } from 'server/providers/services/users.service';
|
||||
|
||||
@Controller()
|
||||
export class UsersController {
|
||||
constructor(private usersService: UsersService) {}
|
||||
|
||||
@Post('/users')
|
||||
async create(
|
||||
@Body() userPayload: CreateUserDto,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
const newUser = new User();
|
||||
newUser.email = userPayload.email;
|
||||
newUser.name = userPayload.name;
|
||||
newUser.password_hash = await bcrypt.hash(userPayload.password, 10);
|
||||
|
||||
try {
|
||||
const user = await this.usersService.create(newUser);
|
||||
// assume signup and write cookie
|
||||
// Write JWT to cookie and send with response.
|
||||
const token = jwt.sign(
|
||||
{
|
||||
user_id: user.id,
|
||||
},
|
||||
process.env.ENCRYPTION_KEY,
|
||||
{ expiresIn: '1h' },
|
||||
);
|
||||
res.cookie('_token', token);
|
||||
return { user, token };
|
||||
} catch (e) {
|
||||
throw new HttpException(
|
||||
`User creation failed. ${e.message}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,16 +7,16 @@ export = process.env.NODE_ENV === 'development'
|
||||
port: 5432,
|
||||
database: process.env.DATABASE_URL,
|
||||
autoLoadEntities: true,
|
||||
migrations: ['src/database/migrations/*.ts'],
|
||||
migrations: ['server/database/migrations/*.ts'],
|
||||
cli: {
|
||||
migrationsDir: 'src/database/migrations',
|
||||
migrationsDir: 'server/database/migrations',
|
||||
},
|
||||
}
|
||||
: {
|
||||
url: process.env.DATABASE_URL,
|
||||
ssl: { rejectUnauthorized: true },
|
||||
migrations: ['src/database/migrations/*.ts'],
|
||||
migrations: ['server/database/migrations/*.ts'],
|
||||
cli: {
|
||||
migrationsDir: 'src/database/migrations',
|
||||
migrationsDir: 'server/database/migrations',
|
||||
},
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ export class AddUser1637028716848 implements MigrationInterface {
|
||||
name: 'id',
|
||||
type: 'int',
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
@ -25,6 +26,7 @@ export class AddUser1637028716848 implements MigrationInterface {
|
||||
name: 'email',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isUnique: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
5
server/dto/create_user.dto.ts
Normal file
5
server/dto/create_user.dto.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class CreateUserDto {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
@ -2,15 +2,15 @@ import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
email: string;
|
||||
@Column({ unique: true, nullable: false })
|
||||
email: string;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
password_hash: string;
|
||||
@Column({ nullable: false })
|
||||
password_hash: string;
|
||||
}
|
@ -2,9 +2,9 @@ import './env';
|
||||
import * as fs from 'fs';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { join } from 'path';
|
||||
import * as morgan from 'morgan';
|
||||
import { AppModule } from './app.module';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
let httpsOptions;
|
||||
@ -16,8 +16,10 @@ async function bootstrap() {
|
||||
}
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||
httpsOptions,
|
||||
logger: ['verbose'],
|
||||
});
|
||||
app.use(morgan('tiny'));
|
||||
|
||||
app.use(cookieParser());
|
||||
app.useStaticAssets(join(__dirname, '..', 'static'));
|
||||
app.setBaseViewsDir(join(__dirname, '../', 'views'));
|
||||
app.setViewEngine('hbs');
|
||||
|
@ -2,10 +2,12 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from 'server/entities/user.entity';
|
||||
import { SessionsController } from '../controllers/sessions.controller';
|
||||
import { UsersController } from 'server/controllers/users.controller';
|
||||
import { UsersService } from '../providers/services/users.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User])],
|
||||
controllers: [SessionsController],
|
||||
controllers: [SessionsController, UsersController],
|
||||
providers: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
@ -19,9 +19,17 @@ export class UsersService {
|
||||
return this.usersRespository.findOne(id);
|
||||
}
|
||||
|
||||
create(user: User) {
|
||||
return this.usersRespository.save(user);
|
||||
}
|
||||
|
||||
async verify(email: string, password: string) {
|
||||
const user = await this.usersRespository.findOne({ email });
|
||||
if (!user) return false;
|
||||
return bcrypt.compare(password, user.password_hash);
|
||||
if (!user) return { verified: false, user: null };
|
||||
const verified: boolean = await bcrypt.compare(
|
||||
password,
|
||||
user.password_hash,
|
||||
);
|
||||
return { verified, user };
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.SETTINGS = {};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" />
|
||||
|
28
yarn.lock
28
yarn.lock
@ -2310,13 +2310,6 @@ base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
basic-auth@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
|
||||
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
|
||||
dependencies:
|
||||
safe-buffer "5.1.2"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
@ -3284,11 +3277,6 @@ depd@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
depd@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
des.js@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
|
||||
@ -6003,17 +5991,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
morgan@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
|
||||
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
|
||||
dependencies:
|
||||
basic-auth "~2.0.1"
|
||||
debug "2.6.9"
|
||||
depd "~2.0.0"
|
||||
on-finished "~2.3.0"
|
||||
on-headers "~1.0.2"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
@ -6264,11 +6241,6 @@ on-finished@^2.3.0, on-finished@~2.3.0:
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
on-headers@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
|
||||
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
|
||||
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
|
Loading…
Reference in New Issue
Block a user