basic registration/login/session
This commit is contained in:
parent
44510096d1
commit
f3cae4b90f
|
@ -0,0 +1,14 @@
|
|||
FROM node:latest
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
#RUN apk update && apk upgrade # && apk add --update alpine-sdk && apk add --update python
|
||||
RUN npm install
|
||||
# If you are building your code for production
|
||||
# RUN npm ci --omit=dev
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8080
|
||||
CMD [ "node", "src/index.js" ]
|
|
@ -0,0 +1,23 @@
|
|||
CREATE TABLE IF NOT EXISTS Users (
|
||||
ID SERIAL,
|
||||
lastname VARCHAR(32),
|
||||
firstname VARCHAR(32),
|
||||
middlename VARCHAR(32),
|
||||
password_hash CHAR(60), --nodejs bcrypt.
|
||||
salt CHAR(29), -- nodejs bcrypt.
|
||||
chats INT[] -- to table Chats, column ID.
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Chats (
|
||||
ID SERIAL,
|
||||
name VARCHAR(32), --chat name
|
||||
admins INT[], -- to table Users, column ID.
|
||||
messages INT[] -- ref to table Messages, column ID.
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Messages (
|
||||
ID SERIAL,
|
||||
author_id INT, -- ref to table Users, column ID.
|
||||
time_sent TIMESTAMP,
|
||||
content TEXT
|
||||
);
|
|
@ -0,0 +1,33 @@
|
|||
version: "3.3"
|
||||
|
||||
services:
|
||||
chat:
|
||||
build: .
|
||||
ports:
|
||||
- 8080:8080
|
||||
networks:
|
||||
ne_nuzhen:
|
||||
ipv4_address: 10.5.0.5
|
||||
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: 'postgres:15'
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
ne_nuzhen:
|
||||
ipv4_address: 10.5.0.6
|
||||
|
||||
environment:
|
||||
POSTGRES_USER: smk # The PostgreSQL user (useful to connect to the database)
|
||||
POSTGRES_PASSWORD: CHANGEME # The PostgreSQL password (useful to connect to the database)
|
||||
POSTGRES_DB: chat # The PostgreSQL default database (automatically created at first launch)
|
||||
|
||||
networks:
|
||||
ne_nuzhen:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 10.5.0.0/16
|
||||
gateway: 10.5.0.1
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "chat",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "test.js",
|
||||
"scripts": {
|
||||
"test": "nodemon test.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.foxarmy.org/leca/smk-chat"
|
||||
},
|
||||
"author": "leca",
|
||||
"license": "WTFPL",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookie-session": "^2.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"nodemon": "^3.1.3",
|
||||
"pg": "^8.12.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<mxfile host="app.diagrams.net" modified="2024-06-11T11:00:11.476Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" etag="D0Ei79GTjtOxs9i7zUn2" version="24.5.2" type="device">
|
||||
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
|
||||
<mxGraphModel dx="1279" dy="748" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||
<mxCell id="GfNOIcLhO2e9G-0w8RP9-0" value="Users" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
||||
<mxGeometry y="80" width="240" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="GfNOIcLhO2e9G-0w8RP9-1" value="ID SERIAL" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="GfNOIcLhO2e9G-0w8RP9-0" vertex="1">
|
||||
<mxGeometry y="30" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="GfNOIcLhO2e9G-0w8RP9-2" value="lastname VARCHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="GfNOIcLhO2e9G-0w8RP9-0" vertex="1">
|
||||
<mxGeometry y="60" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-1" value="firstname VARCHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="GfNOIcLhO2e9G-0w8RP9-0">
|
||||
<mxGeometry y="90" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-2" value="middlename VARCHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="GfNOIcLhO2e9G-0w8RP9-0">
|
||||
<mxGeometry y="120" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="GfNOIcLhO2e9G-0w8RP9-3" value="password_hash CHAR(60)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="GfNOIcLhO2e9G-0w8RP9-0" vertex="1">
|
||||
<mxGeometry y="150" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-0" value="salt CHAR(29)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="GfNOIcLhO2e9G-0w8RP9-0">
|
||||
<mxGeometry y="180" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-4" value="chats INT[]" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="GfNOIcLhO2e9G-0w8RP9-0">
|
||||
<mxGeometry y="210" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-5" value="Chats" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||
<mxGeometry x="687" y="110" width="140" height="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-6" value="ID SERIAL" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-5">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-7" value="name VARCHAR(32)" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-5">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-21" value="admins<span style="background-color: initial;">&nbsp;INT[]</span>" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-5">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-8" value="messages INT[]" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-5">
|
||||
<mxGeometry y="120" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-9" value="Messages" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||
<mxGeometry x="360" y="360" width="240" height="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-10" value="ID SERIAL" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-9">
|
||||
<mxGeometry y="30" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-11" value="author_id INT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-9">
|
||||
<mxGeometry y="60" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-12" value="time_sent TIMESTAMP" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-9">
|
||||
<mxGeometry y="90" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-23" value="content TEXT" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="5QsmdMTZOQ-KcOoPIG_9-9">
|
||||
<mxGeometry y="120" width="240" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;startArrow=ERmandOne;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="5QsmdMTZOQ-KcOoPIG_9-4" target="5QsmdMTZOQ-KcOoPIG_9-6">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="400" y="370" as="sourcePoint" />
|
||||
<mxPoint x="500" y="270" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-20" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToOne;startArrow=ERmandOne;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="5QsmdMTZOQ-KcOoPIG_9-8" target="5QsmdMTZOQ-KcOoPIG_9-10">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="160" y="500" as="sourcePoint" />
|
||||
<mxPoint x="260" y="400" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-22" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;startArrow=ERmandOne;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="5QsmdMTZOQ-KcOoPIG_9-21" target="GfNOIcLhO2e9G-0w8RP9-1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="505" y="550" as="sourcePoint" />
|
||||
<mxPoint x="815" y="440" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="5QsmdMTZOQ-KcOoPIG_9-24" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToOne;startArrow=ERmandOne;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="5QsmdMTZOQ-KcOoPIG_9-11" target="GfNOIcLhO2e9G-0w8RP9-1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="150" y="480" as="sourcePoint" />
|
||||
<mxPoint x="250" y="380" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
|
@ -0,0 +1,131 @@
|
|||
const express = require('express');
|
||||
const pg = require('pg');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const bcrypt = require('bcrypt');
|
||||
const cors = require("cors");
|
||||
const cookieSession = require("cookie-session");
|
||||
|
||||
const app = express();
|
||||
|
||||
const { Client } = pg;
|
||||
const client = new Client({
|
||||
user: "smk",
|
||||
password: "CHANGEME", // do not forget to change it in docker-compose.yml in db section.
|
||||
host: "10.5.0.6", //defined in docker-compose.yml.
|
||||
port: 5432,
|
||||
database: "chat"
|
||||
})
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(require('express-session')({
|
||||
|
||||
name: 'smk_chat',
|
||||
secret: 'PLEASE!!GENERATE!!A!!STRONG!!ONE',
|
||||
resave: false,
|
||||
saveUninitialized: false
|
||||
|
||||
}));
|
||||
|
||||
let sessions = {}
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
if (sessions[req.session.token] == undefined) return res.redirect('/login');
|
||||
res.sendFile('views/index.html', { root: __dirname });
|
||||
});
|
||||
|
||||
app.get('/registration', (req, res) => {
|
||||
if (req.session.token != undefined) return res.redirect('/');
|
||||
|
||||
res.sendFile('views/registration.html', { root: __dirname });
|
||||
});
|
||||
|
||||
app.get('/login', (req, res) => {
|
||||
if (sessions[req.session.token] != undefined) return res.redirect('/');
|
||||
res.sendFile('views/login.html', { root: __dirname });
|
||||
});
|
||||
|
||||
const generateRandomString = () => {
|
||||
return Math.floor(Math.random() * Date.now()).toString(36);
|
||||
};
|
||||
|
||||
|
||||
const getIdByCredentials = async (lastname, firstname, middlename) => {
|
||||
let query_res = await client.query("SELECT ID FROM Users WHERE lastname = $1::text AND firstname = $2::text AND middlename = $3::text;", [lastname, firstname, middlename]);
|
||||
if (query_res.rowCount == 0) return -1; // no such user
|
||||
if (query_res.rowCount == 1) return query_res.rows[0].id;
|
||||
}
|
||||
|
||||
app.get('/api/logout', (req, res) => {
|
||||
if (req.session.token == undefined) return res.redirect('/login');
|
||||
sessions[req.session.token] = undefined;
|
||||
res.redirect('/login');
|
||||
})
|
||||
|
||||
app.post('/api/register', async (req, res) => {
|
||||
try {
|
||||
const { lastname, firstname, middlename, password } = req.body;
|
||||
|
||||
const salt = bcrypt.genSaltSync(10);
|
||||
const hash = bcrypt.hashSync(password, salt);
|
||||
|
||||
if (await getIdByCredentials(lastname, firstname, middlename) > -1) {
|
||||
return res.status(400).send("Such user exists.").end();
|
||||
}
|
||||
|
||||
let id = (await client.query(
|
||||
"INSERT INTO Users (lastname, firstname, middlename, password_hash, salt) VALUES ($1, $2, $3, $4, $5) RETURNING ID;",
|
||||
[lastname, firstname, middlename, hash, salt]
|
||||
)).rows[0].id;
|
||||
req.session.token = generateRandomString();
|
||||
sessions[req.session.token] = id;
|
||||
res.redirect('/');
|
||||
} catch (err) {
|
||||
console.log("[ERROR] in /api/register: " + err)
|
||||
res.status(500).send();
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/login', async (req, res) => {
|
||||
try {
|
||||
const { lastname, firstname, middlename, password } = req.body;
|
||||
|
||||
const ID = await getIdByCredentials(lastname, firstname, middlename)
|
||||
if (ID == -1) {
|
||||
return res.status(400).send("No such user.").end();
|
||||
}
|
||||
|
||||
let stored_password = (await client.query("SELECT password_hash FROM Users WHERE ID = $1;", [ID])).rows[0].password_hash;
|
||||
|
||||
if (bcrypt.compareSync(password, stored_password)) {
|
||||
req.session.token = generateRandomString()
|
||||
sessions[req.session.token] = ID;
|
||||
return res.redirect('/');
|
||||
} else {
|
||||
return res.status(400).send("Wrong password").end();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("[ERROR] in /api/login: " + err)
|
||||
res.status(500).send();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const initDb = async () => {
|
||||
await client.connect()
|
||||
|
||||
let db_schema = fs.readFileSync('./db.psql').toString();
|
||||
try {
|
||||
const res = await client.query(db_schema);
|
||||
console.log("Database initialized.")
|
||||
} catch (err) {
|
||||
console.log("Cannot initialize database. Error: " + err);
|
||||
}
|
||||
}
|
||||
|
||||
initDb().then(() => {
|
||||
app.listen(8080, "0.0.0.0", () => {
|
||||
console.log("Ready to use.");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>index</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/api/logout">Выйти</a>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>login</title>
|
||||
</head>
|
||||
<body>
|
||||
<body>
|
||||
<form method="post" action="/api/login">
|
||||
<label for="lastname">Фамилия:</label><br/>
|
||||
<input type="text" id="lastname" name="lastname"><br/>
|
||||
<label for="firstname">Имя:</label><br/>
|
||||
<input type="text" id="firstname" name="firstname"><br/>
|
||||
<label for="middlename">Отчество:</label><br/>
|
||||
<input type="text" id="middlename" name="middlename"><br/>
|
||||
<label for="password">Пароль:</label><br/>
|
||||
<input type="text" id="password" name="password"><br/>
|
||||
<input type="submit" value="Войти">
|
||||
</form>
|
||||
Нет аккаунта? <a href="/register">Зарегистрируйте.</a>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>registration</title>
|
||||
</head>
|
||||
<body>
|
||||
<form onSubmit="return checkPassword(this)" method="post" action="/api/register">
|
||||
<label for="lastname">Фамилия:</label><br/>
|
||||
<input type="text" id="lastname" name="lastname"><br/>
|
||||
<label for="firstname">Имя:</label><br/>
|
||||
<input type="text" id="firstname" name="firstname"><br/>
|
||||
<label for="middlename">Отчество:</label><br/>
|
||||
<input type="text" id="middlename" name="middlename"><br/>
|
||||
<label for="password">Пароль:</label><br/>
|
||||
<input type="text" id="password" name="password"><br/>
|
||||
<label for="confirmPassword">Повтор пароля:</label><br/>
|
||||
<input type="text" id="confirmPassword" name="confirmPassword"><br/>
|
||||
<input type="submit" value="Зарегистрироваться">
|
||||
</form>
|
||||
Уже есть аккаунт? <a href="/login">Войдите.</a>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
function checkPassword(form) {
|
||||
const password = form.password.value;
|
||||
const confirmPassword = form.confirmPassword.value;
|
||||
|
||||
if (password != confirmPassword) {
|
||||
alert("Error! Password did not match.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
</html>
|
Loading…
Reference in New Issue