fix: support local workshop authentication
This commit is contained in:
13 files changed
+42
-6
No files matched your search
@@ -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.
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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!`;
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
Reference in new issue
Block a user