Install this skill

Paste this into Claude Code (VS Code panel, Adom editor, or terminal) to install:

Search the Adom Wiki for the skill "OAuth" (slug: oauth) at https://wiki-ufypy5dpx93o.adom.cloud/wiki/skills/oauth and install it into my local ~/.claude/skills/oauth/ directory. Fetch the skill_source content from the wiki page and save it as SKILL.md. Then confirm it's installed by showing the first 5 lines.
?
What is a skill? Skills are instructions that teach AI assistants like Claude Code how to perform specific tasks. The description below is loaded into the AI as context when you invoke this skill. Well-written skills make the AI significantly more effective. Like Wikipedia, anyone can improve a skill by clicking Edit AI Skill — or have your AI submit an edit on your behalf.

Description

Edit AI Skill

Adom OAuth

Add OAuth 2.0 authentication to any Adom service using the central OAuth Gateway. Users click one button, authorize in their browser, and they're connected. No per-user setup, no CLI commands.

Architecture

Every Adom user container has a unique hostname. OAuth providers (Google, GitHub, etc.) require a fixed redirect_uri. The OAuth Gateway solves this by providing a single static callback URL that routes auth codes to the correct container via WebSocket.

User clicks "Connect" in their container
  → Container connects to OAuth Gateway via WebSocket
  → Container registers a unique state token
  → User is redirected to OAuth provider (Google, GitHub, etc.)
     with redirect_uri = https://<gateway-host>/callback
  → User authorizes the app
  → Provider redirects to Gateway: /callback?code=XXX&state=YYY
  → Gateway looks up which WebSocket owns that state
  → Sends the auth code back over WebSocket
  → Container exchanges code for tokens locally
  → Container disconnects from Gateway

Key properties:

  • One fixed redirect URI for all users, all services, all providers
  • Credentials and tokens never pass through the gateway — only the auth code
  • Gateway is stateless — just routes state tokens to WebSocket connections
  • Connections are short-lived (connect, get code, disconnect)

OAuth Gateway Service

Location: gallia/services/oauth-gateway/ Port: 8795 Container: john-service-oauth-4e370c6271ae0433 Callback URL: https://oauth-4e370c62.adom.cloud/callback

HTTP Endpoints

MethodRouteDescription
GET/healthHealth check with pending count and uptime
GET/callbackOAuth callback — routes to the container that registered the state
GET/statusJSON status (pending count, uptime)

WebSocket Protocol

Clients connect to the gateway via WebSocket and exchange JSON messages:

Client → Gateway:

{ "type": "register", "state": "<unique-uuid>", "provider": "google" }
{ "type": "unregister", "state": "<unique-uuid>" }

Gateway → Client:

{ "type": "registered", "state": "<uuid>" }
{ "type": "callback", "state": "<uuid>", "code": "<auth-code>", "query": { ... } }
{ "type": "error", "state": "<uuid>", "error": "<message>" }

Registrations auto-expire after 10 minutes. When a WebSocket disconnects, all its registrations are cleaned up.

Client Library

Location: gallia/services/oauth-gateway/client.js

Quick Start — startOAuthFlow()

The simplest way to add OAuth to any service:

import { startOAuthFlow } from '../services/oauth-gateway/client.js';

// 1. Start the flow (connects to gateway, generates state, builds auth URL)
const flow = startOAuthFlow({
  provider: 'google-youtube',
  authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
  clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
  scopes: 'https://www.googleapis.com/auth/youtube.upload',
  extraParams: { access_type: 'offline', prompt: 'consent' },
});

// 2. Redirect the user to the auth URL
// (in an HTTP handler: res.writeHead(302, { Location: flow.authRedirectUrl }))

// 3. Wait for the gateway to deliver the auth code
const { code } = await flow.waitForCode();

// 4. Exchange the code for tokens (using your app's client secret)
const tokens = await exchangeCodeForTokens(code, flow.redirectUri);

Environment Variables

VariableDefaultDescription
OAUTH_GATEWAY_URLhttps://oauth-4e370c62.adom.cloudGateway HTTP URL (for building redirect_uri)
OAUTH_GATEWAY_WSwss://oauth-4e370c62.adom.cloudGateway WebSocket URL

These defaults are hardcoded in client.js — no env vars needed on user containers.

Adding OAuth to a New Service

Step 1: Bundle App Credentials

Store your OAuth client ID and secret in a JSON file in your service directory. This ships with the gallia repo — all users get the same app credentials.

// your-service/credentials.json
{
  "clientId": "YOUR_CLIENT_ID.apps.googleusercontent.com",
  "clientSecret": "YOUR_CLIENT_SECRET"
}

Register the gateway's callback URL as an authorized redirect URI in the provider's console:

https://<gateway-host>/callback

Step 2: Per-User Token Storage

Each user's tokens go in their home directory:

import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { dirname } from 'path';
import { homedir } from 'os';

const TOKEN_PATH = `${homedir()}/.config/your-service-tokens.json`;

function loadTokens() {
  if (!existsSync(TOKEN_PATH)) return null;
  return JSON.parse(readFileSync(TOKEN_PATH, 'utf-8'));
}

function saveTokens(tokens) {
  const dir = dirname(TOKEN_PATH);
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2) + '\n');
}

Step 3: Add Auth Route to Your Server

import { startOAuthFlow } from '../services/oauth-gateway/client.js';
import credentials from './credentials.json' with { type: 'json' };

// GET /your-service/auth — starts the OAuth flow
app.get('/your-service/auth', (req, res) => {
  const flow = startOAuthFlow({
    provider: 'your-provider',
    authUrl: 'https://provider.com/oauth/authorize',
    clientId: credentials.clientId,
    scopes: 'scope1 scope2',
    extraParams: { access_type: 'offline' },
  });

  // Redirect user to provider
  res.redirect(flow.authRedirectUrl);

  // Wait for callback in background
  flow.waitForCode().then(async ({ code }) => {
    const tokenRes = await fetch('https://provider.com/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        code,
        client_id: credentials.clientId,
        client_secret: credentials.clientSecret,
        redirect_uri: flow.redirectUri,
        grant_type: 'authorization_code',
      }),
    });
    const tokens = await tokenRes.json();
    saveTokens(tokens);
  }).catch(err => console.error('OAuth failed:', err.message));
});

Step 4: Token Refresh

const TOKEN_URL = 'https://provider.com/oauth/token';
const REFRESH_MARGIN_MS = 60_000;

async function getAccessToken() {
  let tokens = loadTokens();
  if (!tokens?.refreshToken) throw new Error('Not connected');

  // Return cached if still valid
  if (tokens.accessToken && tokens.expiresAt > Date.now() + REFRESH_MARGIN_MS) {
    return tokens.accessToken;
  }

  // Refresh
  const res = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: credentials.clientId,
      client_secret: credentials.clientSecret,
      refresh_token: tokens.refreshToken,
      grant_type: 'refresh_token',
    }),
  });
  const newTokens = await res.json();
  tokens.accessToken = newTokens.access_token;
  tokens.expiresAt = Date.now() + (newTokens.expires_in * 1000);
  if (newTokens.refresh_token) tokens.refreshToken = newTokens.refresh_token;
  saveTokens(tokens);
  return tokens.accessToken;
}

Step 5: UI — Connect Button

In your HTML, show a "Connect" button when the service isn't configured:

// Check connection status
const res = await fetch('/your-service/status');
const { configured } = await res.json();

if (!configured) {
  // Show "Connect" button that opens /your-service/auth in a new tab
  window.open('/your-service/auth', '_blank');
  // Poll /your-service/status every 2s until configured
}

Remote Management via Container Conduit

The OAuth gateway container has Container Conduit installed, so you can manage it remotely from your main gallia editor without opening a separate VS Code session.

Container name: oauth-gateway

Common Operations

Check if the gateway is running:

container_exec on oauth-gateway: curl -sf http://127.0.0.1:8795/health

Check watchdog status:

container_exec on oauth-gateway: curl -sf http://127.0.0.1:8796/status

Restart the gateway (via watchdog):

container_exec on oauth-gateway: curl -sf -X POST http://127.0.0.1:8796/restart

Start/stop the gateway:

container_exec on oauth-gateway: curl -sf -X POST http://127.0.0.1:8796/start
container_exec on oauth-gateway: curl -sf -X POST http://127.0.0.1:8796/stop

View recent logs:

container_exec on oauth-gateway: tail -50 /tmp/oauth-gateway.log

Pull latest code from gallia and restart:

container_exec on oauth-gateway: cd /home/adom/gallia && git pull
container_exec on oauth-gateway: curl -sf -X POST http://127.0.0.1:8796/restart

Full system status (disk, memory, uptime):

container_status on oauth-gateway

Bootstrapping Container Conduit (if reinstall needed)

If the Conduit agent stops working, open a terminal on the OAuth container and run:

curl -sL https://conduit-d4d7f7f2.adom.cloud/agent/install | CC_BASE_URL=https://conduit-f280e93f.adom.cloud CC_NAME=oauth-gateway sudo -E bash

The agent auto-starts on reboot via cron. Config is at /opt/container-conduit/config.json.

Watchdog + Management Dashboard

The service container runs a watchdog (watchdog.js, port 8796) alongside the gateway. It polls /health every 10 seconds and auto-restarts the gateway after 3 consecutive failures.

The watchdog also exposes a control API:

MethodRouteDescription
GET/statusWatchdog state, last health check, restart count
POST/startStart the gateway
POST/stopStop the gateway
POST/restartRestart the gateway
POST/watchdogToggle auto-restart on/off

A management dashboard (dashboard.html) can be pushed to the Adom Viewer on the service container via show-dashboard.sh. This is an ops interface — start/stop/restart buttons, watchdog toggle, and a state-change event log. It's separate from the read-only service dashboard that end users see in AV's dropdown menu.

Both the gateway and watchdog are started by start-oauth-gateway.sh, which is called on container boot.

File Locations

PathDescription
gallia/services/oauth-gateway/server.jsGateway server (port 8795)
gallia/services/oauth-gateway/watchdog.jsWatchdog + control API (port 8796)
gallia/services/oauth-gateway/client.jsClient library for services
gallia/services/oauth-gateway/dashboard.htmlOps management dashboard (pushed to AV)
gallia/services/oauth-gateway/start-oauth-gateway.shStarts gateway + watchdog (idempotent)
gallia/services/oauth-gateway/show-dashboard.shPushes dashboard to Adom Viewer
gallia/services/oauth-gateway/service.jsonService manifest
gallia/youtube/credentials.jsonYouTube app credentials (bundled)
gallia/youtube/youtube-api.jsYouTube token management + upload
~/.config/youtube-tokens.jsonPer-user YouTube tokens

Supported Providers

The gateway is provider-agnostic. Any OAuth 2.0 provider works:

ProviderAuth URLScopes
Google (YouTube)accounts.google.com/o/oauth2/v2/authyoutube.upload
Google (Drive)accounts.google.com/o/oauth2/v2/authdrive.file
GitHubgithub.com/login/oauth/authorizerepo, user
Slackslack.com/oauth/v2/authorizechat:write, etc.

Test Users (IMPORTANT)

Until the app passes Google's OAuth verification, only test users explicitly added in Google Cloud Console can authorize. Anyone not on the list gets a hard block (not just a warning).

To add a test user: Google Cloud Console → OAuth consent screen → Test users → Add users → enter their Gmail address.

When a user hits this error: The YouTube upload dialog in Movie Maker should detect the 403 access_denied error and show a message like: "YouTube access is restricted. Ask an Adom admin to add your Google account as a test user, or email [email protected]." This saves users from a confusing dead end.

To remove the restriction permanently: Submit the app for OAuth verification (Google Cloud Console → OAuth consent screen → Publish app). Google reviews the app and removes the test user gate. This requires a privacy policy URL, homepage, and possibly a demo video.

Google Cloud Setup (for Adom admins)

  1. Create a Google Cloud project at console.cloud.google.com
  2. Enable the required API (e.g. YouTube Data API v3)
  3. Create OAuth credentials → Web application
  4. Add authorized redirect URI: https://oauth-4e370c62.adom.cloud/callback
  5. Copy client ID + secret to the service's credentials.json
  6. Add test users — OAuth consent screen → Test users → add Gmail addresses of anyone who needs access
  7. Submit for OAuth verification when ready (removes "unverified app" / test user restriction)
  8. Request quota increase if needed

Sub-Skills
?
What are Sub-Skills?

Sub-skills are community-contributed AI skill extensions for this component. They teach AI assistants about specific tools, configurators, or workflows.

Examples:

  • A manufacturer’s configuration tool for a motor controller
  • A community-written design guide for an amplifier circuit
  • An automated test/validation script for a sensor module

How to add one: Click Add Sub-Skill, provide the URL to your skill and a brief description. Submissions are reviewed by the Adom team before going live.

No sub-skills yet. Be the first to contribute one!

0 revisions · Updated 2026-04-16 10:56:13