@skyglide.com/mainapp

The Skyglide cloud: the controller that the Skyglide clients talk to, its client contract, and the hub that connects a member's devices into one private network, at Skyglide locations and away from them.

It answers devices (the skyglided service, the skyglide command-line client, the apps), home and venue routers, and the member actions those flows need, and it drives the hub's WireGuard peers and its isolation rules. At venues it is the signup portal behind openNDS's captive portal and takes payments through PayPal. The member and operator portals' user interfaces, private DNS and inline inspection come later.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

The contract

Clients call TypedRequest methods over HTTPS (POST /typedrequest) and receive pushes over the typed socket. Every method is an IReq_ interface in ts_interfaces/requests/; the generated OpenAPI 3.1 document with every contract and type as JSON Schema is schema/contracts.openapi.json (package export ./schema/contracts.openapi.json).

  • Devices: a device-code sign-in (device.signin.start, device.signin.poll) that the member approves, or a join key; device.enroll with the device's own WireGuard public key; device.config.get (the device's address in its member's away subnet, the hub, the member network's prefixes, its devices); device.location.get (is this network my own network at a location?); key rotation, leaving, and pushes when the configuration changes or the device is removed.
  • Routers: home routers pair with a join key and fetch their configuration; venue routers register their key, fetch their desired state, acknowledge it, and report observations and counts.
  • Members: who is signed in (member.me), approving device codes, join keys, devices, home LAN approval, the internet exit. Members sign in with idp.global in the browser (GET /auth/login); member calls ride on that session's cookie, come from the public origin and carry the session's CSRF token as csrf_token (GET /auth/session answers it).

Sign-in

Members sign in with idp.global, through the relying-party layer of @idp.global/sdk: the authorization code flow with PKCE under /auth/ (login, callback, finalize, session, logout), HttpOnly cookie sessions with a session-bound CSRF token. Skyglide stores no password. A member is provisioned under an email address (seed member now, the signup portal later); the first sign-in whose address idp.global confirms binds that idp.global user to the member, and every later sign-in finds the member by that user. Anyone else is refused, and the browser lands on /device?auth=<reason> (not_a_member, email_not_verified, account_conflict, error).

/device is where a member confirms a device: the verification URL every device sign-in hands out (/device?code=XXXX-XXXX). Signed out, it leads through idp.global and back; signed in, it shows the device and its code and confirms or denies it in a form bound to the session.

Refusals carry a code in error.data.code (TRefusalCode): not_authorized, device_removed, rate_limited, …

Signup and payments

At a venue, openNDS sends a captured guest to https://<signup host>/opennds/<site> with its encrypted client details (secure remote FAS, level 3). The portal shows the prices first, signs the guest in with idp.global and starts the payment: a click on the order button opens a checkout window of 10 minutes at 1 Mbit/s (configurable) so the phone reaches PayPal, and openNDS's authmon fetches that authorization from the portal. A guest pass is a PayPal order; the membership is a PayPal subscription. After payment the member sees their Wi-Fi password for skyglide.com (with a WIFI: QR code), a guest sees their pass. Returning members and guests are recognized when they sign in.

A renewal that fails moves the membership to grace for SKYGLIDE_GRACE_DAYS, then to restricted: the member's Wi-Fi password still works but leads only to the portal (/restricted), until they pay again. /cancel ("Verträge hier kündigen") cancels a membership after sign-in; it stays usable until the paid period ends. Every payment gets an invoice at /invoices/<id>, every refund a credit note. PayPal's webhook is POST /paypal/webhook.

Running

SKYGLIDE_PUBLIC_ORIGIN=https://app.skyglide.com \
SKYGLIDE_DB_URL=mongodb://… \
SKYGLIDE_SECRETS_KEY=<32 bytes, base64> \
SKYGLIDE_SESSION_ENCRYPTION_KEY=<32 bytes, base64> SKYGLIDE_CSRF_HMAC_KEY=<32 bytes, base64> \
IDP_ISSUER=https://idp.global IDP_CLIENT_ID=<client id> IDP_CLIENT_SECRET=<client secret> \
SKYGLIDE_HUB_DEVICE_ENDPOINT=hub.skyglide.com:51820 SKYGLIDE_HUB_DEVICE_PUBLIC_KEY=<key> \
SKYGLIDE_HUB_SITE_ENDPOINT=hub.skyglide.com:51821 SKYGLIDE_HUB_SITE_PUBLIC_KEY=<key> \
SKYGLIDE_HUB_AGENT_SOCKET=/run/skyglide/hub.sock \
SKYGLIDE_SIGNUP_ORIGIN=https://join.skyglide.com \
SKYGLIDE_MEMBERSHIP_PRICE=<monthly gross, e.g. 9.90> SKYGLIDE_GUEST_PASSES=<key:hours:price:devices,…> \
SKYGLIDE_CHECKOUT_MINUTES=10 SKYGLIDE_CHECKOUT_KBPS=1000 SKYGLIDE_CHECKOUT_PER_HOUR=3 SKYGLIDE_CHECKOUT_PER_DAY=6 \
SKYGLIDE_HANDOVER_MINUTES=30 SKYGLIDE_GRACE_DAYS=3 SKYGLIDE_RELEASE_DAYS=30 \
SKYGLIDE_PAYPAL_ENV=sandbox SKYGLIDE_PAYPAL_CLIENT_ID=<id> SKYGLIDE_PAYPAL_CLIENT_SECRET=<secret> SKYGLIDE_PAYPAL_WEBHOOK_ID=<id> \
SKYGLIDE_SELLER_NAME=<legal name> SKYGLIDE_SELLER_ADDRESS='<line>|<line>|<line>' SKYGLIDE_SELLER_VAT_ID=<id> \
SKYGLIDE_SELLER_EMAIL=<address> SKYGLIDE_VAT_RATE=19 SKYGLIDE_INVOICE_PREFIX=SG \
node cli.js controller

Optional: SKYGLIDE_PORT (3000), SKYGLIDE_LISTEN_HOSTNAME (127.0.0.1), SKYGLIDE_DB_BACKEND (nosqldb or mongodb), SKYGLIDE_DB_NAME, SKYGLIDE_TRUSTED_PROXIES, SKYGLIDE_PORTAL_ADDRESSES, SKYGLIDE_DEVICE_MTU, SKYGLIDE_SESSION_MAX_AGE_SECONDS (43200), SKYGLIDE_CURRENCY (EUR), SKYGLIDE_PAYPAL_API_BASE (PayPal's, by environment), SKYGLIDE_PAYPAL_CHECKOUT_HOSTS (more hosts for the walled garden), IDP_CA_CERT_FILE (a private CA of the issuer), SKYGLIDE_TLS_CERT_FILE with SKYGLIDE_TLS_KEY_FILE (https without a proxy). A missing or malformed setting stops the start and names itself; idp.global is required. The idp.global client is registered with the redirect URIs <SKYGLIDE_PUBLIC_ORIGIN>/auth/callback and <SKYGLIDE_SIGNUP_ORIGIN>/join/auth/callback. The venues' walled garden is the signup host, idp.global's host and PayPal's checkout hosts. When idp.global cannot be reached at the start, devices and routers keep working, member sign-ins answer as unavailable, and the controller tries again every 30 seconds.

The hub agent runs as root next to the hub's WireGuard interfaces, with a configuration file of its own:

{
  "socket_path": "/run/skyglide/hub.sock",
  "socket_gid": 990,
  "interfaces": [
    { "name": "sgdev", "listen_port": 51820, "private_key_file": "/etc/skyglide/sgdev.key" },
    { "name": "sgsite", "listen_port": 51821, "private_key_file": "/etc/skyglide/sgsite.key" }
  ],
  "egress_interface": "eth0"
}
node cli.js hub-agent --config /etc/skyglide/hub.json

Development

pnpm install
pnpm test
pnpm run test:e2e

pnpm run test:e2e runs the hub agent with userspace WireGuard and three test devices in Docker and checks that a member's devices reach each other through the hub, that members stay apart, that a forged source address and a removed device get nowhere, and that a join key works once. Tests sign members in through the real /auth/ routes against RelyingPartyProviderDouble, the in-process idp.global of @idp.global/sdk/relyingparty/testing (test/helpers.signin.ts).

With a real idp.global on this host, as nevermind.finance does it:

node cli.js devidp     # once per host, keeps running: the local idp.global on https://idp.localhost:8445 (Docker)
node cli.js preview    # the controller on https://localhost:3200/device, one member per persona

devidp runs @idp.global/devidp (its personas testuser, testmoderator and testsuperadmin; their passwords are in the handoff, $XDG_RUNTIME_DIR/skyglide-devidp/runtime.json) and needs Node 24 or 25: its kernel secret store refuses other versions. preview takes SKYGLIDE_PREVIEW_PORT (3200 to 3263, the callbacks the local idp.global knows), an in-memory database and throwaway keys; trust the handoff's ca.pem in the browser profile you test with.

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
The Skyglide cloud: the controller for apps, servers and routers, its client contract, and the hub that connects them.
Readme MIT
319 KiB
0 Stars 3 Watchers 0 Forks
Languages
TypeScript 99.7%
JavaScript 0.2%