From 63c02f62aa3c57f72602a9efe89dc0780d6d3079 Mon Sep 17 00:00:00 2001 From: Joseph Ditton Date: Sat, 20 Nov 2021 18:18:58 -0700 Subject: [PATCH] basic login boilerplatre --- .gitignore | 1 + .prettierrc | 3 +- client/app.jsx | 41 +++------------ client/components/home/_home.jsx | 3 ++ client/components/router.jsx | 10 ++++ client/components/sign_in/_sign_in.jsx | 3 ++ client/index.js | 2 +- client/package.json | 3 +- client/yarn.lock | 34 +++++++++++++ package.json | 3 +- server/controllers/sessions.controller.ts | 43 ++++++++++++---- server/controllers/user.controller.ts | 0 server/controllers/users.controller.ts | 50 +++++++++++++++++++ server/database/cli_config.ts | 8 +-- .../migrations/1637028716848-AddUser.ts | 2 + server/dto/create_user.dto.ts | 5 ++ server/entities/user.entity.ts | 18 +++---- server/main.ts | 8 +-- server/modules/users.module.ts | 4 +- server/providers/services/users.service.ts | 12 ++++- views/index.hbs | 4 +- yarn.lock | 28 ----------- 22 files changed, 186 insertions(+), 99 deletions(-) create mode 100644 client/components/home/_home.jsx create mode 100644 client/components/router.jsx create mode 100644 client/components/sign_in/_sign_in.jsx delete mode 100644 server/controllers/user.controller.ts create mode 100644 server/controllers/users.controller.ts create mode 100644 server/dto/create_user.dto.ts diff --git a/.gitignore b/.gitignore index ff139c6..8f54d1e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ dist static node_modules +.parcel-cache # Logs logs diff --git a/.prettierrc b/.prettierrc index dcb7279..368186a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { "singleQuote": true, - "trailingComma": "all" + "trailingComma": "all", + "printWidth": 120 } \ No newline at end of file diff --git a/client/app.jsx b/client/app.jsx index 556623f..5ae62df 100644 --- a/client/app.jsx +++ b/client/app.jsx @@ -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 ( - <> -
- setEmail(e.target.value)} - /> -
-
- setPassword(e.target.value)} - /> -
- - + + + ); }; - -export default App; diff --git a/client/components/home/_home.jsx b/client/components/home/_home.jsx new file mode 100644 index 0000000..59389ad --- /dev/null +++ b/client/components/home/_home.jsx @@ -0,0 +1,3 @@ +export const Home = () => { + return
I am the home page
; +}; diff --git a/client/components/router.jsx b/client/components/router.jsx new file mode 100644 index 0000000..a40b06e --- /dev/null +++ b/client/components/router.jsx @@ -0,0 +1,10 @@ +import { Routes, Route } from 'react-router-dom'; +import { Home } from './home/_home'; + +export const Router = () => { + return ( + + } /> + + ); +}; diff --git a/client/components/sign_in/_sign_in.jsx b/client/components/sign_in/_sign_in.jsx new file mode 100644 index 0000000..099ecd6 --- /dev/null +++ b/client/components/sign_in/_sign_in.jsx @@ -0,0 +1,3 @@ +export const SignIn = () => { + return
I am the sign in page
; +}; diff --git a/client/index.js b/client/index.js index bab4dcd..762ab1b 100644 --- a/client/index.js +++ b/client/index.js @@ -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); diff --git a/client/package.json b/client/package.json index e69cc8e..dfce878 100644 --- a/client/package.json +++ b/client/package.json @@ -8,6 +8,7 @@ "devDependencies": {}, "dependencies": { "react": "^17.0.2", - "react-dom": "^17.0.2" + "react-dom": "^17.0.2", + "react-router-dom": "6" } } diff --git a/client/yarn.lock b/client/yarn.lock index 347c5c2..c39acd0 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -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" diff --git a/package.json b/package.json index a9a2d6d..3bdf462 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/controllers/sessions.controller.ts b/server/controllers/sessions.controller.ts index 884ad3c..3b179ad 100644 --- a/server/controllers/sessions.controller.ts +++ b/server/controllers/sessions.controller.ts @@ -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 }; } } diff --git a/server/controllers/user.controller.ts b/server/controllers/user.controller.ts deleted file mode 100644 index e69de29..0000000 diff --git a/server/controllers/users.controller.ts b/server/controllers/users.controller.ts new file mode 100644 index 0000000..773b110 --- /dev/null +++ b/server/controllers/users.controller.ts @@ -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, + ); + } + } +} diff --git a/server/database/cli_config.ts b/server/database/cli_config.ts index ca7104a..e75cce8 100644 --- a/server/database/cli_config.ts +++ b/server/database/cli_config.ts @@ -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', }, }; diff --git a/server/database/migrations/1637028716848-AddUser.ts b/server/database/migrations/1637028716848-AddUser.ts index 6241637..2689d49 100644 --- a/server/database/migrations/1637028716848-AddUser.ts +++ b/server/database/migrations/1637028716848-AddUser.ts @@ -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, }, ], }), diff --git a/server/dto/create_user.dto.ts b/server/dto/create_user.dto.ts new file mode 100644 index 0000000..cf87fed --- /dev/null +++ b/server/dto/create_user.dto.ts @@ -0,0 +1,5 @@ +export class CreateUserDto { + name: string; + email: string; + password: string; +} diff --git a/server/entities/user.entity.ts b/server/entities/user.entity.ts index df39f3f..0bc02a7 100644 --- a/server/entities/user.entity.ts +++ b/server/entities/user.entity.ts @@ -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; -} \ No newline at end of file + @Column({ nullable: false }) + password_hash: string; +} diff --git a/server/main.ts b/server/main.ts index 2ed7d94..486bb86 100644 --- a/server/main.ts +++ b/server/main.ts @@ -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(AppModule, { httpsOptions, + logger: ['verbose'], }); - app.use(morgan('tiny')); + + app.use(cookieParser()); app.useStaticAssets(join(__dirname, '..', 'static')); app.setBaseViewsDir(join(__dirname, '../', 'views')); app.setViewEngine('hbs'); diff --git a/server/modules/users.module.ts b/server/modules/users.module.ts index 1c88c75..a59e24d 100644 --- a/server/modules/users.module.ts +++ b/server/modules/users.module.ts @@ -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 {} diff --git a/server/providers/services/users.service.ts b/server/providers/services/users.service.ts index efeef1d..5a2a1fb 100644 --- a/server/providers/services/users.service.ts +++ b/server/providers/services/users.service.ts @@ -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 }; } } diff --git a/views/index.hbs b/views/index.hbs index 8cf9269..fed0488 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -1,6 +1,8 @@ - +
diff --git a/yarn.lock b/yarn.lock index adb65d5..7e39f8e 100644 --- a/yarn.lock +++ b/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"