fix: support local workshop authentication

This commit is contained in:
Thomas Camlong committed 2026-07-14 01:21:53 +02:00
1 parent c72e4d6dfe
commit 55ba12473c
13 files changed
+42 -6

No files matched your search

+1 -1
View File
@@ -37,4 +37,4 @@ Signed-in members can vote to help useful creations rank higher. Report maliciou
Workshop accounts use GitHub OAuth and are separate from users in a self-hosted Homarr instance. Workshop moderators can remove submissions, prevent an account from publishing, disable an account, and resolve reports. These actions require a reason and are recorded.
The Workshop is a central service operated by Homarr at `store.homarr.dev`. Running the backend locally is supported for contributors developing or testing this feature, not as a private production marketplace.
The Workshop is a central service operated by Homarr at `workshop.homarr.dev`. Running the backend locally is supported for contributors developing or testing this feature, not as a private production marketplace.
+2 -1
View File
@@ -17,7 +17,8 @@ const config: Config = {
locales: ["en"],
},
customFields: {
workshopApiUrl: process.env.NEXT_PUBLIC_WORKSHOP_API_URL ?? "https://store.homarr.dev",
workshopApiUrl:
process.env.WORKSHOP_API_URL ?? process.env.NEXT_PUBLIC_WORKSHOP_API_URL ?? "https://workshop.homarr.dev",
},
onBrokenLinks: "throw",
onBrokenAnchors: "throw",
+1
View File
@@ -3,3 +3,4 @@ GITHUB_CLIENT_SECRET=
PB_SUPERUSER_EMAIL=
PB_SUPERUSER_PASSWORD=
PB_EXPOSE_PORT=8090
PB_ALLOWED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003
+3 -1
View File
@@ -1,6 +1,6 @@
# Homarr Workshop backend
PocketBase provides the central Workshop API at `store.homarr.dev`. It is not a per-instance Homarr service. Local deployment is for development and integration testing.
PocketBase provides the central Workshop API at `workshop.homarr.dev`. It is not a per-instance Homarr service. Local deployment is for development and integration testing.
## Local startup
@@ -15,6 +15,8 @@ Use `pnpm dev:workshop:backend` when only the API is needed. If port 8090 is una
The image downloads the official PocketBase `0.39.6` release and verifies it against that release's `checksums.txt`. Migrations and hooks are mounted read-only.
GitHub OAuth configuration is synchronized from the environment on every boot, including for an existing development volume. Local CORS is restricted to the Docusaurus origins in `PB_ALLOWED_ORIGINS`; production must set it to `https://homarr.dev`.
## Roles and account states
- `member`: publish, update/delete owned submissions, vote, and report.
+1
View File
@@ -12,6 +12,7 @@ services:
PB_SUPERUSER_EMAIL: ${PB_SUPERUSER_EMAIL:-}
PB_SUPERUSER_PASSWORD: ${PB_SUPERUSER_PASSWORD:-}
PB_PUBLIC_URL: http://localhost:${PB_EXPOSE_PORT:-8090}
PB_ALLOWED_ORIGINS: ${PB_ALLOWED_ORIGINS:-http://localhost:3003,http://127.0.0.1:3003}
ports:
- "127.0.0.1:${PB_EXPOSE_PORT:-8090}:8090"
volumes:
+1
View File
@@ -12,6 +12,7 @@ echo "Workshop website: http://localhost:3003/workshop"
exec su-exec pocketbase pocketbase serve \
--http=0.0.0.0:8090 \
--origins="${PB_ALLOWED_ORIGINS:-*}" \
--dir=/pb_data \
--hooksDir=/pb_hooks \
--migrationsDir=/pb_migrations
+10
View File
@@ -1,5 +1,15 @@
/// <reference path="../pb_data/types.d.ts" />
onBootstrap((e) => {
e.next();
const users = e.app.findCollectionByNameOrId("users");
const clientId = $os.getenv("GITHUB_CLIENT_ID");
const clientSecret = $os.getenv("GITHUB_CLIENT_SECRET");
users.oauth2.enabled = Boolean(clientId && clientSecret);
users.oauth2.providers = clientId && clientSecret ? [{ name: "github", clientId, clientSecret }] : [];
e.app.save(users);
});
onRecordAuthWithOAuth2Request((e) => {
const utils = require(`${__hooks}/workshop-utils.js`);
if (e.record && e.record.getString("state") === "disabled")
+2
View File
@@ -15,6 +15,8 @@ cleanup
PB_EXPOSE_PORT="$port" \
PB_SUPERUSER_EMAIL="$email" \
PB_SUPERUSER_PASSWORD="$password" \
GITHUB_CLIENT_ID="integration-client" \
GITHUB_CLIENT_SECRET="integration-secret" \
docker compose --file apps/store/docker-compose.yml --project-name "$project" up --detach --build --wait
if ! WORKSHOP_TEST_URL="http://127.0.0.1:$port" \
@@ -9,6 +9,12 @@ assert(baseUrl && superuserEmail && superuserPassword, "Workshop integration tes
const root = new PocketBase(baseUrl);
await root.collection("_superusers").authWithPassword(superuserEmail, superuserPassword);
const authMethods = await root.collection("users").listAuthMethods();
assert.equal(authMethods.oauth2.enabled, true, "GitHub OAuth must be configured from the runtime environment");
assert.deepEqual(
authMethods.oauth2.providers.map((provider) => provider.name),
["github"],
);
const createUser = async (name, role = "member", state = "active") => {
const password = `Workshop${name}Password123!`;
+1 -1
View File
@@ -36,7 +36,7 @@ The abandoned prototype is preserved at `origin/feat/widget-store-prototype-back
- Launch content: custom widgets (`homarr-custom-widget-v2`) and CSS (`homarr-custom-css-v1`).
- Identity: GitHub OAuth only.
- Community features: votes and reports; comments are removed.
- Hosting: one Homarr-operated service at `store.homarr.dev`; overrides are development-only.
- Hosting: one Homarr-operated service at `workshop.homarr.dev`; overrides are development-only.
- Publishing: immediate, with source inspection, reporting, and staff removal.
- Staff: GitHub-authenticated moderators/admins use `/workshop/admin`; PocketBase superusers are bootstrap/emergency access.
+1 -1
View File
@@ -23,7 +23,7 @@
"dev:docs": "pnpm -F @homarr/docs dev",
"dev:workshop": "concurrently --kill-others --names workshop-api,workshop-web \"pnpm dev:workshop:backend\" \"pnpm dev:workshop:website\"",
"dev:workshop:backend": "docker compose -f apps/store/docker-compose.yml up --build --remove-orphans",
"dev:workshop:website": "cross-env NEXT_PUBLIC_WORKSHOP_API_URL=http://localhost:8090 pnpm -F @homarr/docs dev",
"dev:workshop:website": "pnpm -F @homarr/workshop build && pnpm -F @homarr/docs clear && cross-env WORKSHOP_API_URL=http://localhost:8090 NEXT_PUBLIC_WORKSHOP_API_URL=http://localhost:8090 pnpm -F @homarr/docs dev",
"docker:dev": "docker compose -f ./development/development.docker-compose.yml up",
"format": "turbo format --continue -- --check",
"format:fix": "turbo format --continue",
+12
View File
@@ -109,6 +109,18 @@ export class WorkshopClient {
}
async signInWithGitHub() {
let methods: Awaited<ReturnType<ReturnType<PocketBase["collection"]>["listAuthMethods"]>>;
try {
methods = await this.pocketBase.collection("users").listAuthMethods();
} catch (error) {
throw asWorkshopError(error, "Failed to load Workshop authentication methods");
}
if (!methods.oauth2.providers.some((provider) => provider.name === "github")) {
throw new WorkshopError(
"unavailable",
"GitHub login is not configured. Set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET, then restart Workshop.",
);
}
try {
await this.pocketBase.collection("users").authWithOAuth2({ provider: "github" });
return this.currentUser;
+1 -1
View File
@@ -1,7 +1,7 @@
import { customWidgetImportSchema } from "@homarr/validation/custom-widget";
import { z } from "zod";
export const WORKSHOP_API_URL = "https://store.homarr.dev";
export const WORKSHOP_API_URL = "https://workshop.homarr.dev";
export const WORKSHOP_WEB_URL = "https://homarr.dev/workshop";
export const WIDGET_SCHEMA_VERSION = "homarr-custom-widget-v2";
export const CSS_SCHEMA_VERSION = "homarr-custom-css-v1";