Compare commits

..

30 commits

Author SHA1 Message Date
252da5158c Update dependency concurrently to v5.3.0
Some checks failed
ci / build-image (pull_request) Failing after 1m56s
2025-06-12 14:19:33 +00:00
c9c972cd17 Run build on PRs
Some checks failed
ci / build-image (push) Failing after 27s
2025-06-12 15:10:52 +01:00
44b6f40270 Merge pull request 'Update dependency aws-sdk to v2.1692.0' (#19) from renovate/aws-sdk-2.x-lockfile into main
Some checks failed
ci / build-image (push) Failing after 1m41s
Reviewed-on: #19
2025-06-12 13:38:20 +00:00
8f33e77ee2 Update dependency aws-sdk to v2.1692.0 2025-06-12 13:37:56 +00:00
7e4fbb5685 Merge pull request 'Update dependency koa-views to v6.3.1' (#10) from renovate/koa-views-6.x-lockfile into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #10
2025-06-12 13:37:46 +00:00
5b5569184f Update dependency koa-views to v6.3.1 2025-06-12 13:37:40 +00:00
90aeca84d0 Merge pull request 'Update redis Docker tag to v4.0.14' (#16) from renovate/redis-4.x into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #16
2025-06-12 13:36:39 +00:00
e104a864f9 Update redis Docker tag to v4.0.14 2025-06-12 13:36:31 +00:00
759a03a56c Merge pull request 'Update dependency tmp to v0.2.3' (#14) from renovate/tmp-0.x into main
Some checks failed
ci / build-image (push) Failing after 2s
Reviewed-on: #14
2025-06-12 13:09:48 +00:00
31e1572f03 Update dependency tmp to v0.2.3 2025-06-12 13:08:48 +00:00
9868a8d8cd Merge pull request 'Update dependency nodemon to v2.0.22' (#13) from renovate/nodemon-2.x-lockfile into main
Some checks failed
ci / build-image (push) Failing after 2s
Reviewed-on: #13
2025-06-12 13:08:35 +00:00
14d8461a8b Update dependency nodemon to v2.0.22 2025-06-12 13:06:50 +00:00
2e0e53043c Merge pull request 'Update dependency koa-compress to v5.1.1' (#9) from renovate/koa-compress-5.x-lockfile into main
Some checks failed
ci / build-image (push) Failing after 1m4s
Reviewed-on: #9
2025-06-12 12:12:53 +00:00
e0a9db71a7 Update dependency koa-compress to v5.1.1 2025-06-12 12:12:17 +00:00
60ca7e666a Merge pull request 'Update dependency form-data to v3.0.3' (#8) from renovate/form-data-3.x-lockfile into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #8
2025-06-12 12:11:45 +00:00
12bb6aba90 Update dependency form-data to v3.0.3 2025-06-12 12:11:10 +00:00
4621a82693 Merge pull request 'Update dependency ejs to v3.1.10' (#7) from renovate/ejs-3.x-lockfile into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #7
2025-06-12 12:10:58 +00:00
0a1ae819b6 Update dependency ejs to v3.1.10 2025-06-12 12:10:48 +00:00
460b483299 Merge pull request 'Update dependency bootstrap-sass to v3.4.3' (#6) from renovate/bootstrap-sass-3.x-lockfile into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #6
2025-06-12 12:10:36 +00:00
60303c8c82 Update dependency bootstrap-sass to v3.4.3 2025-06-12 12:10:28 +00:00
30885dd00a Merge pull request 'Update dependency angular-reconnecting-websocket to v0.1.1' (#3) from renovate/angular-reconnecting-websocket-0.x into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #3
2025-06-12 12:10:18 +00:00
410c58cc7f Update dependency angular-reconnecting-websocket to v0.1.1 2025-06-12 12:09:54 +00:00
2802ed0c4f Merge pull request 'Update dependency angular-route to v1.8.3' (#5) from renovate/angular-route-1.x-lockfile into main
Some checks failed
ci / build-image (push) Has been cancelled
Reviewed-on: #5
2025-06-12 12:09:31 +00:00
571adcc8aa Update dependency angular-route to v1.8.3 2025-06-12 12:09:20 +00:00
fa94bbf7f4 Merge pull request 'Update dependency angular-resource to v1.8.3' (#4) from renovate/angular-resource-1.x-lockfile into main
Some checks are pending
ci / build-image (push) Waiting to run
Reviewed-on: #4
2025-06-12 12:09:11 +00:00
4928ba991c Update dependency angular-resource to v1.8.3 2025-06-12 12:08:59 +00:00
a26f8bf775 Merge pull request 'Update dependency angular to v1.8.3' (#2) from renovate/angular-1.x-lockfile into main
Some checks are pending
ci / build-image (push) Waiting to run
Reviewed-on: #2
2025-06-12 12:08:39 +00:00
06f786e421 Update dependency angular to v1.8.3 2025-06-11 23:02:22 +00:00
7b40a0efae Update dependency mime-types to v2.1.35
All checks were successful
ci / build-image (push) Successful in 2m9s
2025-06-11 22:03:22 +00:00
994bc2ef4c Merge pull request 'Configure Renovate' (#1) from renovate/configure into main
All checks were successful
ci / build-image (push) Successful in 55s
Reviewed-on: #1
2025-06-11 15:53:05 +00:00
11 changed files with 1590 additions and 633 deletions

View file

@ -1,25 +0,0 @@
export DEBUG="hostr*"
export NODE_ENV=development
export PORT=4040
export WEB_BASE_URL=http://localhost:$PORT
export API_BASE_URL=$WEB_BASE_URL/api
export UPLOAD_STORAGE_PATH=$HOME/.hostr/uploads
export COOKIE_KEY=INSECURE
export EMAIL_FROM=
export EMAIL_NAME=
export STATSD_HOST=localhost
export DATABASE_URL=postgresql://hostr:hostr@database:5432/hostr
export REDIS_URL=redis://localhost:6379
export SENDGRID_KEY=
export STRIPE_SECRET_KEY=
export STRIPE_PUBLIC_KEY=
# optional, some functionality will be disabled
export AWS_ENDPOINT= # only for AWS-like providers, not AWS
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
export AWS_BUCKET=
export VIRUSTOTAL_KEY=
export SENTRY_DSN=

View file

@ -1,12 +0,0 @@
WEB_BASE_URL=http://localhost:3000
API_BASE_URL=http://localhost:3000/api
UPLOAD_STORAGE_PATH=/hostr/uploads
COOKIE_KEY=TESTING
EMAIL_FROM=jonathan@hostr.co
EMAIL_NAME="Jonathan from Hostr"
DATABASE_URL=postgresql://hostr:hostr@database:5432/hostr
REDIS_URL=redis://redis:6379
AWS_ENDPOINT=http://minio:9000
AWS_ACCESS_KEY_ID=7HYV3KPRGQ8Z5YCDNWC6
AWS_SECRET_ACCESS_KEY=0kWP/ZkgIwQzgL9t4SGv9Uc93rO//OdyqMH329b/
AWS_BUCKET=hostr

View file

@ -0,0 +1,41 @@
name: ci
on:
push:
branches: main
pull_request:
types: [opened, synchronize, reopened]
jobs:
build-image:
runs-on: self-hosted
steps:
- name: Set current date as env variable
run: echo "NOW=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_ENV
- name: Fix for bad os check
run: echo "RUNNER_OS=Linux" >> $GITHUB_ENV
- name: Login to Forgejo Registry
uses: https://cremin.dev/actions/podman-login@v1
with:
registry: cremin.dev
username: ${{ github.actor }}
password: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Check out repository
uses: https://cremin.dev/actions/checkout@v4
- name: Build image
uses: https://cremin.dev/actions/buildah-build@v2
with:
containerfiles: ./Containerfile
context: ./
oci: true
layers: true
image: hostr
tags: latest ${{ github.sha }}
- name: Push image
uses: https://cremin.dev/actions/push-to-registry@v2
with:
registry: cremin.dev/jonathan
username: ${{ github.actor }}
password: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
image: hostr
tags: latest ${{ github.sha }}

View file

@ -1,57 +0,0 @@
name: ci
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened]
jobs:
build-image:
runs-on: self-hosted
steps:
- name: Set current date as env variable
run: echo "NOW=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_ENV
- name: Fix for bad os check
run: echo "RUNNER_OS=Linux" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: https://cremin.dev/actions/docker-login@v3
with:
registry: cremin.dev
username: ${{ github.actor }}
password: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Check out repository
uses: https://cremin.dev/actions/checkout@v4
- name: Set up Docker Buildx
uses: https://cremin.dev/actions/docker-setup-buildx@v3
- name: Build and push
uses: https://cremin.dev/actions/docker-build-push@v6
with:
file: ./Containerfile
context: ./
tags: cremin.dev/jonathan/hostr:latest,cremin.dev/jonathan/hostr:${{ github.sha }}
push: true
test-image:
runs-on: node22
needs: build-image
services:
database:
image: postgres:14-alpine
env:
POSTGRES_PASSWORD: hostr
POSTGRES_USER: hostr
POSTGRES_DB: hostr
redis:
image: redis:4.0.2-alpine
minio:
image: minio/minio
env:
MINIO_ACCESS_KEY: 7HYV3KPRGQ8Z5YCDNWC6
MINIO_SECRET_KEY: 0kWP/ZkgIwQzgL9t4SGv9Uc93rO//OdyqMH329b/
cmd: ["server", "/export"]
steps:
- name: Check out repository
uses: https://cremin.dev/actions/checkout@v4
- name: Test image
run: |
docker run --env-file ./.forgejo/workflows/.env --rm -it cremin.dev/jonathan/hostr:${{ github.sha }} yarn test

3
.gitignore vendored
View file

@ -1,4 +1,4 @@
.envrc
.env*
.DS_Store
.sass-cache/
node_modules
@ -7,7 +7,6 @@ jspm_packages
npm-debug.log
web/public/build
web/public/styles/*.css
web/public/styles/*.css.map
*.gz
minigun*.json
test*.json

View file

@ -1,4 +1,4 @@
FROM node:22.16
FROM node:16.13.1
WORKDIR /app

View file

@ -10,36 +10,36 @@ help:
.PHONY: build
build: ## Run `yarn run build`
podman compose run --rm app yarn run build
docker-compose run --rm app yarn run build
.PHONY: test
test: ## Run tests
podman compose run --rm app yarn test
docker-compose run --rm app yarn test
.PHONY: logs
logs: ## Tail the app and worker logs
podman compose logs -f app worker
docker-compose logs -f app worker
.PHONY: migrate
migrate: ## Migrate database schema
podman compose run --rm app yarn run initdb
docker-compose run --rm app yarn run initdb
.PHONY: init
init: ## Migrate database schema
podman compose run --rm app yarn run init
docker-compose run --rm app yarn run init
.PHONY: watch-frontend
watch-frontend: ## Build and watch for changes
podman compose run --rm app yarn run watch
docker-compose run --rm app yarn run watch
.PHONY: podman compose-up
podman compose-up: ## Start (and create) docker containers
podman compose up -d
.PHONY: docker-compose-up
docker-compose-up: ## Start (and create) docker containers
docker-compose up -d
.PHONY: yarn
yarn: ## Update yarn dependencies
podman compose run --rm app yarn
docker-compose run --rm app yarn
.PHONY: shell
shell: ## Run shell
podman compose run --rm app sh
docker-compose run --rm app sh

View file

@ -61,7 +61,7 @@ services:
- "3001:3000"
command: yarn run worker
database:
image: "postgres:14-alpine"
image: "postgres:10-alpine"
ports:
- "5432:5432"
environment:
@ -69,7 +69,7 @@ services:
POSTGRES_USER: "hostr"
POSTGRES_DB: "hostr"
redis:
image: "redis:8-alpine"
image: "redis:4.0.14-alpine"
ports:
- "6379:6379"
minio:

View file

@ -5,14 +5,14 @@
"version": "0.0.0",
"private": true,
"engines": {
"node": ">=22.0.0"
"node": ">=11.0.0"
},
"scripts": {
"build": "yarn run build-js && yarn run build-sass",
"build-js": "NODE_OPTIONS=--openssl-legacy-provider webpack --progress -p -c webpack.config.js",
"build-sass": "sass --load-path=./node_modules/ web/public/styles/",
"build-js": "webpack --progress -p -c webpack.config.js",
"build-sass": "node-sass --include-path ./node_modules/ -r -o web/public/styles/ web/public/styles/",
"heroku-postbuild": "yarn run build",
"init": "node -e \"require('./lib/storage').default();\"",
"init": "babel-node -e \"require('./lib/storage').default();\"",
"initdb": "node -r babel-register test/initdb.js",
"lint": "eslint .",
"start": "node -r babel-register app.js",
@ -21,7 +21,7 @@
"watch": "concurrently -k -n watch-js,watch-sass \"yarn run watch-js\" \"yarn run watch-sass\"",
"watch-js": "webpack -w --mode=development --progress -c webpack.config.js",
"watch-server": "nodemon -r babel-register -i web/public",
"watch-sass": "sass --load-path=./node_modules/ -w web/public/styles/"
"watch-sass": "node-sass --include-path ./node_modules/ -w -r -o web/public/styles/ web/public/styles/"
},
"dependencies": {
"@sendgrid/mail": "^7.1.1",
@ -75,12 +75,11 @@
"moment": "^2.24.0",
"mz": "^2.7.0",
"node-fetch": "^2.3.0",
"node-sass": "^7.0.0",
"node-uuid": "^1.4.8",
"nodemon": "^3.1.10",
"passwords": "^1.3.1",
"pg": "^8.0.3",
"redis": "^3.0.2",
"sass": "^1.89.2",
"sequelize": "^5.21.11",
"smooth-scroll": "https://github.com/cferdinandi/smooth-scroll#5.3.7",
"statsy": "~0.2.0",
@ -96,7 +95,8 @@
"eslint-config-airbnb": "^18.1.0",
"eslint-plugin-import": "^2.20.2",
"mocha": "^8.0.0",
"nodemon": "^2.0.2",
"supertest": "^4.0.2",
"tmp": "0.2.1"
"tmp": "0.2.3"
}
}

View file

@ -1,29 +1,29 @@
import crypto from "crypto";
import { join } from "path";
import passwords from "passwords";
import uuid from "node-uuid";
import views from "co-views";
import debugname from "debug";
import sendgrid from "@sendgrid/mail";
import models from "../../models";
import crypto from 'crypto';
import { join } from 'path';
import passwords from 'passwords';
import uuid from 'node-uuid';
import views from 'co-views';
import debugname from 'debug';
import sendgrid from '@sendgrid/mail';
import models from '../../models';
const render = views(join(__dirname, "..", "views"), { default: "ejs" });
const debug = debugname("hostr-web:auth");
const render = views(join(__dirname, '..', 'views'), { default: 'ejs' });
const debug = debugname('hostr-web:auth');
sendgrid.setApiKey(process.env.SENDGRID_KEY);
const from = process.env.EMAIL_FROM;
const fromname = process.env.EMAIL_NAME;
export async function authenticate(email, password) {
const remoteIp = this.headers["x-forwarded-for"] || this.ip;
const remoteIp = this.headers['x-forwarded-for'] || this.ip;
if (!password || password.length < 6) {
debug("No password, or password too short");
return new Error("Invalid login details");
debug('No password, or password too short');
return new Error('Invalid login details');
}
const count = await models.login.count({
where: {
ip: remoteIp.split(",")[0],
ip: remoteIp.split(',')[0],
successful: false,
createdAt: {
$gt: Math.ceil(Date.now()) - 600000,
@ -32,8 +32,8 @@ export async function authenticate(email, password) {
});
if (count > 25) {
debug("Throttling brute force");
return new Error("Invalid login details");
debug('Throttling brute force');
return new Error('Invalid login details');
}
const user = await models.user.findOne({
where: {
@ -43,29 +43,30 @@ export async function authenticate(email, password) {
});
const login = await models.login.create({
ip: remoteIp.split(",")[0],
ip: remoteIp.split(',')[0],
successful: false,
});
if (user && user.password) {
login.userId = user.id;
if (await passwords.verify(password, user.password)) {
debug("Password verified");
debug('Password verified');
login.successful = true;
await login.save();
return user;
}
debug("Password invalid");
debug('Password invalid');
}
await login.save();
return false;
}
export async function setupSession(user) {
debug("Setting up session");
const token = uuid.v4();
await this.redis.set(token, user.id, "EX", 604800);
export async function setupSession(user) {
debug('Setting up session');
const token = uuid.v4();
debug(user)
await this.redis.set(token, user.id, 'EX', 604800);
const sessionUser = {
id: user.id,
@ -75,26 +76,27 @@ export async function setupSession(user) {
joined: user.createdAt,
plan: user.plan,
uploadsToday: await models.file.count({ userId: user.id }),
md5: crypto.createHash("md5").update(user.email).digest("hex"),
md5: crypto.createHash('md5').update(user.email).digest('hex'),
token,
};
if (sessionUser.plan === "Pro") {
if (sessionUser.plan === 'Pro') {
sessionUser.maxFileSize = 524288000;
sessionUser.dailyUploadAllowance = "unlimited";
sessionUser.dailyUploadAllowance = 'unlimited';
}
this.session.user = sessionUser;
if (this.request.body.remember && this.request.body.remember === "on") {
if (this.request.body.remember && this.request.body.remember === 'on') {
const remember = await models.remember.create({
id: uuid(),
userId: user.id,
});
this.cookies.set("r", remember.id, { maxAge: 1209600000, httpOnly: true });
this.cookies.set('r', remember.id, { maxAge: 1209600000, httpOnly: true });
}
debug("Session set up");
debug('Session set up');
}
export async function signup(email, password, ip) {
const existingUser = await models.user.findOne({
where: {
@ -103,29 +105,26 @@ export async function signup(email, password, ip) {
},
});
if (existingUser) {
debug("Email already in use.");
throw new Error("Email already in use.");
debug('Email already in use.');
throw new Error('Email already in use.');
}
const cryptedPassword = await passwords.crypt(password);
const user = await models.user.create(
{
const user = await models.user.create({
email,
password: cryptedPassword,
ip,
plan: 'Free',
activation: {
id: uuid(),
email,
password: cryptedPassword,
ip,
plan: "Free",
activation: {
id: uuid(),
email,
},
},
{
include: [models.activation],
},
);
}, {
include: [models.activation],
});
await user.save();
const html = await render("email/inlined/activate", {
const html = await render('email/inlined/activate', {
activationUrl: `${process.env.WEB_BASE_URL}/activate/${user.activation.id}`,
});
const text = `Thanks for signing up to Hostr!
@ -137,15 +136,18 @@ ${process.env.WEB_BASE_URL}/activate/${user.activation.id}
`;
sendgrid.send({
to: user.email,
subject: "Welcome to Hostr",
subject: 'Welcome to Hostr',
from,
fromname,
html,
text,
categories: ["activate"],
categories: [
'activate',
],
});
}
export async function sendResetToken(email) {
const user = await models.user.findOne({
where: {
@ -157,7 +159,7 @@ export async function sendResetToken(email) {
id: uuid.v4(),
userId: user.id,
});
const html = await render("email/inlined/forgot", {
const html = await render('email/inlined/forgot', {
forgotUrl: `${process.env.WEB_BASE_URL}/forgot/${reset.id}`,
});
const text = `It seems you've forgotten your password :(
@ -165,32 +167,38 @@ Visit ${process.env.WEB_BASE_URL}/forgot/${reset.id} to set a new one.
`;
sendgrid.send({
to: user.email,
from: "jonathan@hostr.co",
fromname: "Jonathan from Hostr",
subject: "Hostr Password Reset",
from: 'jonathan@hostr.co',
fromname: 'Jonathan from Hostr',
subject: 'Hostr Password Reset',
html,
text,
categories: ["password-reset"],
categories: [
'password-reset',
],
});
} else {
throw new Error("There was an error looking up your email address.");
throw new Error('There was an error looking up your email address.');
}
}
export async function fromToken(token) {
const userId = await this.redis.get(token);
return models.user.findByPk(userId);
}
export async function fromCookie(rememberId) {
const userId = await models.remember.findByPk(rememberId);
return models.user.findByPk(userId);
}
export async function validateResetToken(resetId) {
return models.reset.findByPk(resetId);
}
export async function updatePassword(userId, password) {
const cryptedPassword = await passwords.crypt(password);
const user = await models.user.findByPk(userId);
@ -198,6 +206,7 @@ export async function updatePassword(userId, password) {
await user.save();
}
export async function activateUser(code) {
const activation = await models.activation.findOne({
where: {

1926
yarn.lock

File diff suppressed because it is too large Load diff