First commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.vscode/settings.json
|
||||||
|
project.zip
|
||||||
13
authserver/Dockerfile
Normal file
13
authserver/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM denoland/deno:alpine-1.29.2
|
||||||
|
|
||||||
|
EXPOSE 7777
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY deps.js .
|
||||||
|
|
||||||
|
RUN deno cache deps.js
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD [ "run", "--unstable", "--watch", "--allow-all", "--no-check", "app-launch.js" ]
|
||||||
7
authserver/app-launch.js
Normal file
7
authserver/app-launch.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { app } from "./app.js";
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
port: 7777,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.listen(options);
|
||||||
19
authserver/app.js
Normal file
19
authserver/app.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Application, Session } from "./deps.js";
|
||||||
|
import { authMiddleware } from "./middlewares/authMiddleware.js";
|
||||||
|
import { errorMiddleware } from "./middlewares/errorMiddleware.js";
|
||||||
|
import { renderMiddleware } from "./middlewares/renderMiddleware.js";
|
||||||
|
import { userMiddleware } from "./middlewares/userMiddleware.js";
|
||||||
|
import { serveStaticMiddleware } from "./middlewares/serveStaticMiddleware.js";
|
||||||
|
import { router } from "./routes/routes.js";
|
||||||
|
|
||||||
|
const app = new Application();
|
||||||
|
app.use(Session.initMiddleware());
|
||||||
|
app.use(errorMiddleware);
|
||||||
|
app.use(authMiddleware);
|
||||||
|
app.use(userMiddleware);
|
||||||
|
app.use(serveStaticMiddleware);
|
||||||
|
app.use(renderMiddleware);
|
||||||
|
app.use(router.routes());
|
||||||
|
app.use(router.allowedMethods());
|
||||||
|
|
||||||
|
export { app };
|
||||||
10
authserver/database/database.js
Normal file
10
authserver/database/database.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { postgres } from "../deps.js";
|
||||||
|
|
||||||
|
let sql;
|
||||||
|
if (Deno.env.get("DATABASE_URL")) {
|
||||||
|
sql = postgres(Deno.env.get("DATABASE_URL"));
|
||||||
|
} else {
|
||||||
|
sql = postgres({});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sql };
|
||||||
13
authserver/deps.js
Normal file
13
authserver/deps.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export { configure, renderFile } from "https://deno.land/x/eta@v2.0.0/mod.ts";
|
||||||
|
export {
|
||||||
|
Application,
|
||||||
|
Router,
|
||||||
|
send,
|
||||||
|
} from "https://deno.land/x/oak@v11.1.0/mod.ts";
|
||||||
|
import { readFileSync } from "https://deno.land/std@0.152.0/node/fs.ts";
|
||||||
|
export { readFileSync };
|
||||||
|
import postgres from "https://deno.land/x/postgresjs@v3.3.3/mod.js";
|
||||||
|
export { postgres };
|
||||||
|
export { Session } from "https://deno.land/x/oak_sessions@v4.0.5/mod.ts";
|
||||||
|
export * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts";
|
||||||
|
export * as validasaur from "https://deno.land/x/validasaur@v0.15.0/mod.ts";
|
||||||
38
authserver/fly.toml
Normal file
38
authserver/fly.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# fly.toml file generated for authapp on 2023-04-20T11:14:23+03:00
|
||||||
|
|
||||||
|
app = "authapp"
|
||||||
|
kill_signal = "SIGINT"
|
||||||
|
kill_timeout = 5
|
||||||
|
primary_region = "arn"
|
||||||
|
processes = []
|
||||||
|
|
||||||
|
[env]
|
||||||
|
|
||||||
|
[experimental]
|
||||||
|
auto_rollback = true
|
||||||
|
|
||||||
|
[[services]]
|
||||||
|
http_checks = []
|
||||||
|
internal_port = 7777
|
||||||
|
processes = ["app"]
|
||||||
|
protocol = "tcp"
|
||||||
|
script_checks = []
|
||||||
|
[services.concurrency]
|
||||||
|
hard_limit = 25
|
||||||
|
soft_limit = 20
|
||||||
|
type = "connections"
|
||||||
|
|
||||||
|
[[services.ports]]
|
||||||
|
force_https = true
|
||||||
|
handlers = ["http"]
|
||||||
|
port = 80
|
||||||
|
|
||||||
|
[[services.ports]]
|
||||||
|
handlers = ["tls", "http"]
|
||||||
|
port = 443
|
||||||
|
|
||||||
|
[[services.tcp_checks]]
|
||||||
|
grace_period = "1s"
|
||||||
|
interval = "15s"
|
||||||
|
restart_limit = 0
|
||||||
|
timeout = "2s"
|
||||||
17
authserver/middlewares/authMiddleware.js
Normal file
17
authserver/middlewares/authMiddleware.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const restrictedPaths = ["/index"];
|
||||||
|
|
||||||
|
const authMiddleware = async (context, next) => {
|
||||||
|
const user = await context.state.session.get("user");
|
||||||
|
if (
|
||||||
|
!user &&
|
||||||
|
restrictedPaths.some((path) =>
|
||||||
|
context.request.url.pathname.startsWith(path)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
context.response.redirect("/auth/login");
|
||||||
|
} else {
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { authMiddleware };
|
||||||
9
authserver/middlewares/errorMiddleware.js
Normal file
9
authserver/middlewares/errorMiddleware.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const errorMiddleware = async (context, next) => {
|
||||||
|
try {
|
||||||
|
await next();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { errorMiddleware };
|
||||||
24
authserver/middlewares/renderMiddleware.js
Normal file
24
authserver/middlewares/renderMiddleware.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { configure, renderFile } from "../deps.js";
|
||||||
|
|
||||||
|
const renderMiddleware = async (context, next) => {
|
||||||
|
configure({
|
||||||
|
views: `${Deno.cwd()}/views/`,
|
||||||
|
});
|
||||||
|
|
||||||
|
context.render = async (file, data) => {
|
||||||
|
if (!data) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.user) {
|
||||||
|
data.user = context.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.response.headers.set("Content-Type", "text/html; charset=utf-8");
|
||||||
|
context.response.body = await renderFile(file, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { renderMiddleware };
|
||||||
15
authserver/middlewares/serveStaticMiddleware.js
Normal file
15
authserver/middlewares/serveStaticMiddleware.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { send } from "../deps.js";
|
||||||
|
|
||||||
|
const serveStaticMiddleware = async (context, next) => {
|
||||||
|
if (context.request.url.pathname.startsWith("/static")) {
|
||||||
|
const path = context.request.url.pathname.substring(7);
|
||||||
|
|
||||||
|
await send(context, path, {
|
||||||
|
root: `${Deno.cwd()}/static`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { serveStaticMiddleware };
|
||||||
14
authserver/middlewares/userMiddleware.js
Normal file
14
authserver/middlewares/userMiddleware.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as authService from "../services/authService.js";
|
||||||
|
|
||||||
|
const userMiddleware = async (context, next) => {
|
||||||
|
const user = await context.state.session.get("user");
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const userFromDatabase = await authService.findUser(user.username);
|
||||||
|
context.user = userFromDatabase[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { userMiddleware };
|
||||||
85
authserver/routes/controllers/authController.js
Normal file
85
authserver/routes/controllers/authController.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import * as authService from "../../services/authService.js";
|
||||||
|
import { bcrypt, validasaur } from "../../deps.js";
|
||||||
|
|
||||||
|
const validationRules = {
|
||||||
|
username: [validasaur.required],
|
||||||
|
password: [validasaur.minLength(8), validasaur.required],
|
||||||
|
};
|
||||||
|
|
||||||
|
const showLogin = ({ render }) => {
|
||||||
|
render("login.eta");
|
||||||
|
};
|
||||||
|
|
||||||
|
const showRegister = ({ render }) => {
|
||||||
|
render("register.eta");
|
||||||
|
};
|
||||||
|
|
||||||
|
const register = async ({ request, response, render }) => {
|
||||||
|
const body = request.body({ type: "form" });
|
||||||
|
const params = await body.value;
|
||||||
|
const userData = {
|
||||||
|
username: params.get("username"),
|
||||||
|
password: params.get("password"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const userDatabase = await authService.findUser(userData.username);
|
||||||
|
if (userDatabase.length > 0) {
|
||||||
|
response.status = 422;
|
||||||
|
render("register.eta", {
|
||||||
|
errors: { error: { message: "Username already registered!" } },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [passes, errors] = await validasaur.validate(userData, validationRules);
|
||||||
|
|
||||||
|
if (!passes) {
|
||||||
|
response.status = 422;
|
||||||
|
userData.errors = errors;
|
||||||
|
render("register.eta", userData);
|
||||||
|
} else {
|
||||||
|
const hashedPassword = await bcrypt.hash(userData.password);
|
||||||
|
await authService.createUser(userData.username, hashedPassword);
|
||||||
|
render("register.eta", {
|
||||||
|
errors: { error: { message: "Registration successful!" } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async ({ request, response, state, render }) => {
|
||||||
|
const body = request.body({ type: "form" });
|
||||||
|
const params = await body.value;
|
||||||
|
const username = params.get("username");
|
||||||
|
const userDatabase = await authService.findUser(username);
|
||||||
|
if (userDatabase.length < 1) {
|
||||||
|
response.status = 422;
|
||||||
|
render("login.eta", {
|
||||||
|
errors: { error: { message: "Please enter correct username!" } },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = userDatabase[0];
|
||||||
|
const passwordCorrect = await bcrypt.compare(
|
||||||
|
params.get("password"),
|
||||||
|
user.password
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!passwordCorrect) {
|
||||||
|
response.status = 422;
|
||||||
|
render("login.eta", {
|
||||||
|
errors: { error: { message: "Please enter correct password!" } },
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await state.session.set("user", user);
|
||||||
|
response.redirect("/index");
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async ({ response, state }) => {
|
||||||
|
await state.session.set("user", null);
|
||||||
|
response.redirect("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
export { showLogin, showRegister, register, login, logout };
|
||||||
6
authserver/routes/controllers/homeController.js
Normal file
6
authserver/routes/controllers/homeController.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const showHome = async ({ state, render }) => {
|
||||||
|
const user = await state.session.get("user");
|
||||||
|
render("index.eta", user);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { showHome };
|
||||||
5
authserver/routes/controllers/mainController.js
Normal file
5
authserver/routes/controllers/mainController.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const showMain = async ({ render }) => {
|
||||||
|
render("main.eta");
|
||||||
|
};
|
||||||
|
|
||||||
|
export { showMain };
|
||||||
20
authserver/routes/routes.js
Normal file
20
authserver/routes/routes.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Router } from "../deps.js";
|
||||||
|
import * as mainController from "./controllers/mainController.js";
|
||||||
|
import * as authController from "./controllers/authController.js";
|
||||||
|
import * as homeController from "./controllers/homeController.js";
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
// mainController routes (home page)
|
||||||
|
router.get("/", mainController.showMain);
|
||||||
|
|
||||||
|
// authController routes (login and register)
|
||||||
|
router.get("/auth/login", authController.showLogin);
|
||||||
|
router.get("/auth/register", authController.showRegister);
|
||||||
|
router.post("/auth/login", authController.login);
|
||||||
|
router.post("/auth/register", authController.register);
|
||||||
|
router.get("/logout", authController.logout);
|
||||||
|
|
||||||
|
router.get("/index", homeController.showHome);
|
||||||
|
|
||||||
|
export { router };
|
||||||
11
authserver/services/authService.js
Normal file
11
authserver/services/authService.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { sql } from "../database/database.js";
|
||||||
|
|
||||||
|
const findUser = async (username) => {
|
||||||
|
return await sql`SELECT * FROM users WHERE username = ${username}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUser = async (username, password) => {
|
||||||
|
return await sql`INSERT INTO users (username, password) VALUES (${username}, ${password})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { findUser, createUser };
|
||||||
9
authserver/views/index.eta
Normal file
9
authserver/views/index.eta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<% layout("./layouts/layout.eta") %>
|
||||||
|
<h1>Online Forum</h1>
|
||||||
|
|
||||||
|
<h2>Welcome!</h2>
|
||||||
|
|
||||||
|
<p>You are authenticated, welcome <%= it.username %>!</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
35
authserver/views/layouts/layout.eta
Normal file
35
authserver/views/layouts/layout.eta
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/papercss@1.8.2/dist/paper.min.css">
|
||||||
|
<title>Online Forum</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<% if (it.user) { %>
|
||||||
|
<nav class="border fixed split-nav">
|
||||||
|
<div class="nav-brand">
|
||||||
|
<h3>Hello <%= it.user.username %>!</h3>
|
||||||
|
</div>
|
||||||
|
<div class="collapsible">
|
||||||
|
<input id="collapsible1" type="checkbox" name="collapsible1">
|
||||||
|
<label for="collapsible1">
|
||||||
|
<div class="bar1"></div>
|
||||||
|
<div class="bar2"></div>
|
||||||
|
</label>
|
||||||
|
<div class="collapsible-body">
|
||||||
|
<ul class="inline">
|
||||||
|
<li><a href="/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<div class="paper container">
|
||||||
|
<%~ it.body %>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
authserver/views/login.eta
Normal file
24
authserver/views/login.eta
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<% layout("./layouts/layout.eta") %>
|
||||||
|
|
||||||
|
<h1>Login</h1>
|
||||||
|
|
||||||
|
<form method="POST" action="/auth/login">
|
||||||
|
Username:<br/>
|
||||||
|
<input type="text" name="username" /><br/>
|
||||||
|
Password:<br/>
|
||||||
|
<input type="password" name="password" /><br/>
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<% if (it.errors) { %>
|
||||||
|
<ul>
|
||||||
|
<% Object.keys(it.errors).forEach((error) => { %>
|
||||||
|
<% Object.values(it.errors[error]).forEach((err) => { %>
|
||||||
|
<li><%= err %></li>
|
||||||
|
<% }); %>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
<% } %>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="/auth/register">Are you a new user?</a>
|
||||||
9
authserver/views/main.eta
Normal file
9
authserver/views/main.eta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<% layout("./layouts/layout.eta") %>
|
||||||
|
|
||||||
|
<h1>Online Forum</h1>
|
||||||
|
|
||||||
|
<p>This is an online forum that allows the participants to join, post questions, answer the posts etc.</p>
|
||||||
|
|
||||||
|
<p>Are you already registered? <a href="/auth/login">Login</a></p>
|
||||||
|
|
||||||
|
<p>Not registered yet? <a href="/auth/register">Register</a></p>
|
||||||
24
authserver/views/register.eta
Normal file
24
authserver/views/register.eta
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<% layout("./layouts/layout.eta") %>
|
||||||
|
|
||||||
|
<h1>New user registration</h1>
|
||||||
|
|
||||||
|
<form method="POST" action="/auth/register">
|
||||||
|
Username:<br/>
|
||||||
|
<input type="text" name="username" /><br/>
|
||||||
|
Password:<br/>
|
||||||
|
<input type="password" name="password" /><br/>
|
||||||
|
<input type="submit" value="Register" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<% if (it.errors) { %>
|
||||||
|
<ul>
|
||||||
|
<% Object.keys(it.errors).forEach((error) => { %>
|
||||||
|
<% Object.values(it.errors[error]).forEach((err) => { %>
|
||||||
|
<li><%= err %></li>
|
||||||
|
<% }); %>
|
||||||
|
<% }); %>
|
||||||
|
</ul>
|
||||||
|
<% } %>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="/auth/login">Are you already registered?</a>
|
||||||
34
docker-compose.yml
Normal file
34
docker-compose.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
drill-and-practice:
|
||||||
|
build: authserver
|
||||||
|
image: authserver
|
||||||
|
restart: "no"
|
||||||
|
volumes:
|
||||||
|
- ./authserver/:/app
|
||||||
|
ports:
|
||||||
|
- 7777:7777
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
- flyway
|
||||||
|
env_file:
|
||||||
|
- project.env
|
||||||
|
|
||||||
|
database:
|
||||||
|
container_name: database-p2-e8774b8e-c7a9-4cce-a779-3b59be02206d
|
||||||
|
image: postgres:14.1
|
||||||
|
restart: "no"
|
||||||
|
env_file:
|
||||||
|
- project.env
|
||||||
|
|
||||||
|
flyway:
|
||||||
|
image: flyway/flyway:9.11.0-alpine
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
volumes:
|
||||||
|
- .:/flyway/sql
|
||||||
|
command: -connectRetries=60 -baselineOnMigrate=true migrate
|
||||||
|
env_file:
|
||||||
|
- project.env
|
||||||
|
|
||||||
6
flyway/sql/V1___initial_schema.sql
Normal file
6
flyway/sql/V1___initial_schema.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(255) UNIQUE,
|
||||||
|
password CHAR(60)
|
||||||
|
);
|
||||||
|
|
||||||
16
project.env
Normal file
16
project.env
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Database configuration for PostgreSQL (running in container called "database-p2-e8774b8e-c7a9-4cce-a779-3b59be02206d")
|
||||||
|
POSTGRES_USER=username
|
||||||
|
POSTGRES_PASSWORD=password
|
||||||
|
POSTGRES_DB=database
|
||||||
|
|
||||||
|
# Database configuration for Flyway (used for database migrations)
|
||||||
|
FLYWAY_USER=username
|
||||||
|
FLYWAY_PASSWORD=password
|
||||||
|
FLYWAY_URL=jdbc:postgresql://database-p2-e8774b8e-c7a9-4cce-a779-3b59be02206d:5432/database
|
||||||
|
|
||||||
|
# Database configuration for Deno's PostgreSQL driver
|
||||||
|
PGUSER=username
|
||||||
|
PGPASSWORD=password
|
||||||
|
PGHOST=database-p2-e8774b8e-c7a9-4cce-a779-3b59be02206d
|
||||||
|
PGPORT=5432
|
||||||
|
PGDATABASE=database
|
||||||
Reference in New Issue
Block a user