First commit

This commit is contained in:
AndrewTrieu
2023-04-21 13:17:50 +03:00
commit c9f31e5c8a
26 changed files with 465 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode/settings.json
project.zip

0
README.md Normal file
View File

13
authserver/Dockerfile Normal file
View 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
View File

@@ -0,0 +1,7 @@
import { app } from "./app.js";
const options = {
port: 7777,
};
app.listen(options);

19
authserver/app.js Normal file
View 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 };

View 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
View 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
View 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"

View 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 };

View File

@@ -0,0 +1,9 @@
const errorMiddleware = async (context, next) => {
try {
await next();
} catch (e) {
console.log(e);
}
};
export { errorMiddleware };

View 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 };

View 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 };

View 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 };

View 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 };

View File

@@ -0,0 +1,6 @@
const showHome = async ({ state, render }) => {
const user = await state.session.get("user");
render("index.eta", user);
};
export { showHome };

View File

@@ -0,0 +1,5 @@
const showMain = async ({ render }) => {
render("main.eta");
};
export { showMain };

View 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 };

View 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 };

View File

@@ -0,0 +1,9 @@
<% layout("./layouts/layout.eta") %>
<h1>Online Forum</h1>
<h2>Welcome!</h2>
<p>You are authenticated, welcome <%= it.username %>!</p>

View 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>

View 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>

View 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>

View 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
View 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

View File

@@ -0,0 +1,6 @@
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) UNIQUE,
password CHAR(60)
);

16
project.env Normal file
View 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