Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/saloniamatteo/openalias

Improved OpenAlias API WebForm, with a sleek, modern look
https://github.com/saloniamatteo/openalias

bitcoin btc composer laravel monero oa oa1 open-alias openalias openalias-api openalias-api-webform openalias-webui php xmr

Last synced: about 21 hours ago
JSON representation

Improved OpenAlias API WebForm, with a sleek, modern look

Awesome Lists containing this project

README

        

# [OpenAlias WebUI](https://oa.salonia.it)
OpenAlias web portal to easily retrieve OpenAlias records of any domain.

This website is built in **PHP** with the following:
- **Backend**: [Laravel](https://laravel.com)
- **Bundler**: [Vite](https://vite.dev) + [PurgeCSS](https://purgecss.com)

The following are used for the front-end:
- **Framework**: [CirrusUI](https://cirrus-ui.com)
- **Icons**: [Lucide](https://lucide.dev)

## Donate
Support this project: [salonia.it/donate](https://salonia.it/donate)

## Screenshots
Landing page:
![oa](Pictures/oa.png)

Page with results:
![oa-records](Pictures/oa-records.png)

Page with no results:
![oa-norecords](Pictures/oa-norecords.png)

## Features
### DNSSEC validation
Each requested domain is checked for presence of `RRSIG` records,
which confirm DNSSEC validity.

The validation is done with the following command: `host -t RRSIG `,
where `` corresponds to the requested domain, already sanitized.

The results are then displayed on top of the records table,
showing a white check mark on a green background (✅),
or a white cross on a red background (❎),
followed, respectively, by **DNSSEC OK**, or **DNSSEC FAIL**.

### AbuseIPDB
This Middleware, written by me, checks if the incoming IP address comes from
a "bad" server (crawlers, scanners, etc.), thanks to
[AbuseIPDB](https://www.abuseipdb.com/faq.html)'s `/check` API endpoint.

When a request is received, the `BlockRequest` Middleware will check the cache,
using the incoming IP as key. If a record is found, check if it is a good IP:
if it is, proceed with the request. If it isn't, throw a `403`, which will be
rendered with a pretty page, regardless.

If no records are found in the cache, `BlockRequest` queries AbuseIPDB,
honoring the user-provided options (see below). If the IP address is
whitelisted, check if the user wants to ignore this whitelist;
we then check the IP score and, if it is above a certain threshold,
the request will be blocked, like the case above, throwing a `403`.

To use this, create an account, then head over to [AbuseIPDB/api](https://www.abuseipdb.com/account/api)
and create an APIv2 key. Save this key into the `.env` file:

```env
# If you want to block incoming requests from bad servers
# using AbuseIPDB, enter your API key here.
ABUSEIPDB_KEY= # Your API key goes here!
```

You're all set! Make sure the cache store is also properly configured.
The cache store provided with this site is `file`, so you should be good.

Additionally, you can tune the following parameters:
- `ABUSEIPDB_THRESHOLD`: The minimum percentage score required
for an IP to be considered malicious. Default: `35`.
- `ABUSEIPDB_IGNORE_WHITELIST`: Ignore AbuseIPDB's whitelist preference
for every IP. Default: `0`.
- `ABUSEIPDB_CACHE_TTL`: Store the results in cache for x minutes. Default: `15`
- `ABUSEIPDB_IP_OK`: Store this string for a known good IP. Default: `OK`
- `ABUSEIPDB_IP_BAD`: Store this string for a known bad IP. Default: `BAD`

### Rate limiter
Apart from the AbuseIPDB integration, this website uses Laravel's
[rate limiter](https://laravel.com/docs/11.x/rate-limiting).
It uses the same `CACHE_STORE` driver as the AbuseIPDB integration,
which defaults to `file`.

The rate limiter is defined in `app/Providers/AppServiceProvider.php` as follows:

```php
/* Bootstrap any application services. */
public function boot()
{
// Limit to 5 requests per minute.
RateLimiter::for('global', function (Request $request) {
if (config('APP_ENV') == 'production') {
return Limit::perMinute(5)->by($request->ip());
}
});
}
```

It is configured to allow a maximum of **5 page requests per minute**,
before throwing an HTTP 429 (Too many requests).

### Asset bundling
Assets are bundled and handled by Vite:
- CSS & JS files are minified (PostCSS and PurgeCSS) and versioned
- Images are versioned

This helps with removing unused code, lowering asset size, and lowering page load times.

Run `npm run build` to re-generate the asset bundle.

### Components
Most HTML components (Card, Hero, Tile, etc.) are split up in several files, under `resources/views/components/`.
This makes it way easier and faster to write new pages, thanks to Blade Templates.

### Caching
Config, events, views, and routes are cached, making site load-times faster.

Run `composer cache` to cache them.

### Minification
Every page is minified. Laravel does not do this by default, and there does not
seem to be a "standard" way to do it, other than downloading some shady package.

I've implemented my own simple HTML minifier, making use of PHP's output buffering.

Additionally, CSS & JS files are minified by PurgeCSS and Vite, respectively.

## Dependencies
To deploy this website, you need the following:
- `php`
- `composer`
- `nodejs` with `npm`

## Setup
- Clone the repo: `git clone https://github.com/saloniamatteo/openalias`
- Change directory: `cd openalias`
- Install PHP dependencies: `composer install`
- Install node dependencies: `npm i`
- Generate `APP_KEY`: `php artisan key:generate`

Note that you also may need to change file permissions and/or owner
depending on your setup. If you do, run the following command:

```bash
git config core.fileMode false
```

This stops git from tracking file permission changes.

If you want to deploy the website locally, copy `.env.example` to `.env`:

```bash
cp .env.example .env
```

Make sure you modify `.env`, and uncomment the following:

```env
# Uncomment these values if running locally
APP_ENV=local
APP_DEBUG=true
APP_URL="http://localhost"
```

The website can now be deployed using the built-in webserver, `php artisan serve`:
it will be reachable at `localhost` on port `8000`.

If you want to use the built-in webserver, make sure you set `APP_URL` to your website's URL.

If you want to serve this website to the Internet, please make sure you don't use
`php spark serve`, and rather have a real server.
I use [nginx](https://nginx.org) with [FastCGI](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html).

Make sure you also disable access to `/build/assets/manifest.json`!

When updating, you may use the `update.sh` script under the `scripts/` folder:

```bash
./scripts/update.sh
```

This does the following:
- Installs the composer + npm dependencies
- Generates a new key, forcefully
- Bundles the assets
- Caches config, events, routes, views.

### Assets
Make sure you bundle the assets used in the website (CSS, fonts, images):

```sh
npm run build
```

### Cache
When running in production, it is recommended to cache PHP assets with the following command:

```sh
composer cache
```

This will cache PHP config, events, routes, views.

### Sample nginx config
Note: this config makes the following assumptions:
- Your site is hosted at `oa.example.com`
- You use LetsEncrypt (`certbot`) and have deployed an SSL certificate
- Your `nginx` build supports HTTP2 and HTTP3 (QUIC)
- You have IPv6 support enabled
- You use port 80 for HTTP and port 443 for HTTPS
- You use php-fpm (FastCGI) and call it via `/var/run/php-fpm.sock`
- You want to disable client uploads
- You want to redirect every HTTP request to the HTTPS port
- You want to allow `robots.txt`
- You want to disable `.well-known`

Make sure you movify everything that says "Change this"!

```nginx
# Optional: Rate-limits
#limit_req_zone $binary_remote_addr zone=oa_css:10m rate=10r/s;
#limit_req_zone $binary_remote_addr zone=oa_img:10m rate=5r/s;

# Optional: Bandwidth limits
#limit_conn_zone $binary_remote_addr zone=oa_conn_css:10m;
#limit_conn_zone $binary_remote_addr zone=oa_conn_img:10m;

server {
# HTTP/1.1 & HTTP/2
listen 443 ssl;
listen [::]:443 ssl;

# HTTP/3 (QUIC)
listen 443 quic;
listen [::]:443 quic;

# Change this!
server_name oa.example.com;

# If the host isn't oa.example.com, redirect the client
if ($host != oa.example.com) {
return 301 https://oa.example.com$request_uri;
}

# HTTP2/3
http2 on;
http3 on;
quic_gso on;
quic_retry on;
ssl_early_data on;

# SSL
ssl_stapling on;
ssl_stapling_verify on;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_certificate /etc/letsencrypt/live/oa.example.com/fullchain.pem; # Change this!
ssl_certificate_key /etc/letsencrypt/live/oa.example.com/privkey.pem; # Change this!
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

# Site root
# Change this!
root /var/www/oa/public;

# Prevent nginx HTTP Server Detection
server_tokens off;

# Only allow GET requests
if ($request_method !~* ^GET$) {
return 405;
}

# Disable uploads
client_max_body_size 0;
client_body_timeout 0s;
fastcgi_buffers 64 4K;

# The settings allows you to optimize the HTTP2 bandwidth.
# See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements for tuning hints
client_body_buffer_size 512k;

# Specify how to handle directories -- specifying `/index.php$request_uri`
# here as the fallback means that Nginx always exhibits the desired behaviour
# when a client requests a path that corresponds to a directory that exists
# on the server. In particular, if that directory contains an index.php file,
# that file is correctly served; if it doesn't, then the request is passed to
# the front-end controller. This consistent behaviour means that we don't need
# to specify custom rules for certain paths (e.g. images and other assets,
# `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
# `try_files $uri $uri/ /index.php$request_uri`
# always provides the desired behaviour.
index index.php index.html /index.php$request_uri;

# Allow robots.txt
location = /robots.txt {
allow all;
log_not_found off;
}

# Allow .well-known/security.txt
location = /.well-known/security.txt {
allow all;
log_not_found off;
}

# Prepend all requests with "/index.php" -- this acts as our front controller.
# index.php handles all requests, but we have to hide it.
# The line below allows us to do exactly what we want.
location / {
rewrite ^ /index.php;
}

# Ensure this block, which passes PHP files to the PHP process, is above the blocks
# which handle static assets (as seen below). If this block is not declared first,
# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
# to the URI, resulting in a HTTP 500 error response.
location ~ \.php(?:$|/) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;

# Try to load requested file. If it doesn't exist, instead
# of throwing a 404, load the front controller, where
# we can load a pretty 404 page.
try_files $fastcgi_script_name /index.php/$fastcgi_script_name;

include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;

fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
fastcgi_param front_controller_active true; # Enable pretty urls
fastcgi_pass unix:/var/run/php-fpm.sock;

fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_max_temp_file_size 0;

# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;

# Do not show ratelimit
fastcgi_hide_header X-Ratelimit-Limit;
fastcgi_hide_header X-Ratelimit-Remaining;

# Enable gzip but do not remove ETag headers
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types text/plain;

# Inform clients that HTTP3 is available
add_header Alt-Svc 'h3=":443"; ma=86400';

# COOP/COEP. Disable if you use external plugins/images/assets
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Content Security Policy
# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
# This policy allows only internal assets.
add_header Content-Security-Policy "default-src 'self'; img-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; child-src 'self';";

# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# HTTP response headers borrowed from Nextcloud `.htaccess`
add_header Referrer-Policy "no-referrer";
add_header X-Content-Type-Options "nosniff";
add_header X-Download-Options "noopen";
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Permitted-Cross-Domain-Policies "none";
add_header X-XSS-Protection "0";

# Tell browsers to use per-origin process isolation
add_header Origin-Agent-Cluster "?1" always;
}

# CSS & JS
location ~ \.(?:css|js|woff2)$ {
# Limit access to CSS & JS
# Set a burst of 15, and start delaying after the 10th req.
#limit_req zone=oa_css burst=15 delay=10;
#limit_req_log_level warn;
#limit_req_status 429;

# Cap bandwidth to 1MB/s after 1MB,
# allowing 5 concurrent requests
#limit_conn oa_conn_css 5;
#limit_rate_after 1M;
#limit_rate 1M;

# Enable gzip but do not remove ETag headers
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types font/woff2 text/css text/javascript text/plain;

add_header Alt-Svc 'h3=":443"; ma=86400';
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

try_files $uri /index.php$request_uri;

expires 10d;
access_log off;
}

# Images
location ~ \.(?:gif|ico|jpg|jpeg|pdf|png|svg|webp)$ {
# Limit access to images
# Set a burst of 10, and start delaying after the 5th req.
#limit_req zone=oa_img burst=10 delay=5;
#limit_req_log_level warn;
#limit_req_status 429;

# Cap bandwidth to 1MB/s after 1MB,
# allowing 5 concurrent requests
#limit_conn oa_conn_img 5;
#limit_rate_after 1M;
#limit_rate 1M;

add_header Alt-Svc 'h3=":443"; ma=86400';
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

try_files $uri /index.php$request_uri;

expires 14d;
access_log off;
}
}

server {
listen 80;
listen [::]:80;

# Change this!
server_name oa.example.com;

# Prevent nginx HTTP Server Detection
server_tokens off;

# Change this!
return 301 https://oa.example.com$request_uri;
}
```