Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"source": {
"source": "github",
"repo": "LasVegasForTransit/repository-tooling",
"ref": "v0.4.4"
"ref": "v0.4.5"
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions .github/actions/setup-node-pnpm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ runs:
with:
node-version-file: package.json
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@lasvegasfortransit'

- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ github.token }}
6 changes: 3 additions & 3 deletions .lvbt/web-platform.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"formatVersion": 1,
"preset": "lvbt-web",
"release": "v0.4.4",
"commit": "ffa587b3cc0c217bfd407e202aa45ac5390a1586",
"contentHash": "64c6ddc7019e327d9f6f9ffe276a694ed0b47fafea249f51ee8fee4c99fe7298",
"release": "v0.4.5",
"commit": "402288c0a379a3eaec3922ed5cdd42395a044b3b",
"contentHash": "77cc0e091ed3560e8608d95ff7efc4542cdb05047a9b9ed381f02ce47646f3b7",
"executables": [
"examples/with-astro/.githooks/commit-msg",
"examples/with-astro/.githooks/pre-commit",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"source": {
"source": "github",
"repo": "LasVegasForTransit/repository-tooling",
"ref": "v0.4.4"
"ref": "v0.4.5"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ runs:
with:
node-version-file: package.json
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@lasvegasfortransit'

- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ github.token }}
8 changes: 4 additions & 4 deletions .lvbt/web-platform/examples/with-astro/apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
},
"devDependencies": {
"@astrojs/check": "catalog:",
"@lasvegasfortransit/eslint-config": "0.4.4",
"@lasvegasfortransit/playwright-config": "0.4.4",
"@lasvegasfortransit/typescript-config": "0.4.4",
"@lasvegasfortransit/vitest-config": "0.4.4",
"@lasvegasfortransit/eslint-config": "0.4.5",
"@lasvegasfortransit/playwright-config": "0.4.5",
"@lasvegasfortransit/typescript-config": "0.4.5",
"@lasvegasfortransit/vitest-config": "0.4.5",
"@playwright/test": "catalog:",
"@types/node": "catalog:",
"eslint": "catalog:",
Expand Down
4 changes: 2 additions & 2 deletions .lvbt/web-platform/examples/with-astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"*": "prettier --write --ignore-unknown"
},
"devDependencies": {
"@lasvegasfortransit/cli": "0.4.4",
"@lasvegasfortransit/prettier-config": "0.4.4",
"@lasvegasfortransit/cli": "0.4.5",
"@lasvegasfortransit/prettier-config": "0.4.5",
"lint-staged": "catalog:",
"markdownlint-cli2": "catalog:",
"markdownlint-rule-relative-links": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"source": {
"source": "github",
"repo": "LasVegasForTransit/repository-tooling",
"ref": "v0.4.4"
"ref": "v0.4.5"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ runs:
with:
node-version-file: package.json
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@lasvegasfortransit'

- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ github.token }}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"react-dom": "catalog:"
},
"devDependencies": {
"@lasvegasfortransit/eslint-config": "0.4.4",
"@lasvegasfortransit/playwright-config": "0.4.4",
"@lasvegasfortransit/typescript-config": "0.4.4",
"@lasvegasfortransit/vitest-config": "0.4.4",
"@lasvegasfortransit/eslint-config": "0.4.5",
"@lasvegasfortransit/playwright-config": "0.4.5",
"@lasvegasfortransit/typescript-config": "0.4.5",
"@lasvegasfortransit/vitest-config": "0.4.5",
"@playwright/test": "catalog:",
"@tailwindcss/vite": "catalog:",
"@types/node": "catalog:",
Expand Down
4 changes: 2 additions & 2 deletions .lvbt/web-platform/examples/with-vite-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"*": "prettier --write --ignore-unknown"
},
"devDependencies": {
"@lasvegasfortransit/cli": "0.4.4",
"@lasvegasfortransit/prettier-config": "0.4.4",
"@lasvegasfortransit/cli": "0.4.5",
"@lasvegasfortransit/prettier-config": "0.4.5",
"lint-staged": "catalog:",
"markdownlint-cli2": "catalog:",
"markdownlint-rule-relative-links": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion .lvbt/web-platform/packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lasvegasfortransit/cli",
"version": "0.4.4",
"version": "0.4.5",
"description": "The lvbt command every LVBT repository runs for bootstrap, preflight, and deploy, plus the production platform setup, the shared git hooks, and the lvbt-contributions agent plugin.",
"license": "MIT",
"type": "module",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lvbt-contributions",
"version": "0.4.4",
"version": "0.4.5",
"description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.",
"author": {
"name": "Las Vegans for Better Transit",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lvbt-contributions",
"version": "0.4.4",
"version": "0.4.5",
"description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.",
"author": {
"name": "Las Vegans for Better Transit",
Expand Down
32 changes: 21 additions & 11 deletions .lvbt/web-platform/packages/cli/src/lib/platform/guides.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export const LVBT_GOOGLE_PROJECT_ID = 'lvbt-core';
export const LVBT_SHARED_EMAIL = 'tech@lasvegasfortransit.org';
/** The LVBT Cloudflare account's name. */
export const LVBT_CLOUDFLARE_ACCOUNT = 'Las Vegans for Better Transit';
/**
* The account still carries an old, misspelled name in the dashboard. Every
* guide that opens Cloudflare and chooses the account repeats this check, so
* whoever notices it fixes it once instead of copying the wrong name onward.
*/
const ACCOUNT_NAME_CHECK = `If the account switcher still shows "Las Vegas for Better…" instead of "${LVBT_CLOUDFLARE_ACCOUNT}", open Manage Account and rename it to "${LVBT_CLOUDFLARE_ACCOUNT}" first.`;

const REGIONS = {
'us-east-1': 'North Virginia (us-east-1)',
Expand Down Expand Up @@ -128,7 +134,7 @@ export function zeroTrustGuide(manifest) {
return {
url: ZERO_TRUST,
steps: [
`Open Cloudflare One and choose the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). These steps appear only the first time Zero Trust is used in an account.`,
`Open Cloudflare One and choose the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). These steps appear only the first time Zero Trust is used in an account. ${ACCOUNT_NAME_CHECK}`,
`Cloudflare asks you to choose the team domain (its documentation calls this the team name). Type ${LVBT_TEAM_SUBDOMAIN}, so the team domain becomes ${LVBT_TEAM_DOMAIN}. That is the address of the sign-in page, the value ${holds} holds, and the start of the Google sign-in addresses.`,
`If it also asks for a team name, type ${LVBT_TEAM_NAME}. The team name is only a label people see; it changes nothing in any configuration.`,
'Choose the Zero Trust Free plan. Cloudflare asks for payment details even for the Free plan, but does not charge for it.',
Expand All @@ -143,7 +149,7 @@ export function teamDomainGuide(secretName) {
return {
url: ZERO_TRUST,
steps: [
`Open Cloudflare One and choose the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). If it shows its first-time setup instead, Zero Trust was never turned on for this account: press Enter to skip, and run this command with the Cloudflare token it asks for, so it can show those steps.`,
`Open Cloudflare One and choose the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). If it shows its first-time setup instead, Zero Trust was never turned on for this account: press Enter to skip, and run this command with the Cloudflare token it asks for, so it can show those steps. ${ACCOUNT_NAME_CHECK}`,
`On Overview, find Account details. It shows the Team domain (for LVBT, ${LVBT_TEAM_DOMAIN}) and the Team name ("${LVBT_TEAM_NAME}"), which is only a label. ${secretName} needs the domain.`,
"Copy the Team domain, without https://, and paste it at this command's prompt.",
],
Expand All @@ -166,9 +172,10 @@ export function googleWorkspaceGuide(teamDomain, workspaceDomain, group) {
`On the Clients page, if a client named "Cloudflare Access" is listed, click it, check that it has the two addresses below, and under "Client secrets" click "Add secret", because Google shows a secret only when it is made. Otherwise click "Create client", choose the application type "Web application", and name it Cloudflare Access.`,
`Under "Authorized JavaScript origins", click "Add URI" and enter exactly https://${team}`,
`Under "Authorized redirect URIs", click "Add URI" and enter exactly https://${team}/cdn-cgi/access/callback, then click "Create" (or "Save").`,
'Skip "Credentials" → "Service Accounts" and domain-wide delegation: nothing here uses a service account key, and GitHub Actions authenticates without one. Leave "Disable service account key creation" on if the project asks.',
'Google shows the Client ID and the Client secret. Copy the Client ID (it ends in .apps.googleusercontent.com) and paste it into "App ID" in the Cloudflare tab.',
'Copy the Client secret and paste it into "Client secret" in the Cloudflare tab. Neither value is stored in GitHub or on the Worker.',
`In the Cloudflare tab, type ${domain} as the Google Workspace domain, and click "Save".`,
`In the Cloudflare tab, type ${domain} as the Google Workspace domain. Leave "Proof Key for Code Exchange (PKCE)" on; only turn it off if "Test" (a later step) fails with a code-verifier error. Leave "Enable SCIM" off, along with "Enable user deprovisioning" and "Remove user seat on deprovision", and leave the SCIM identity update behavior as "No action": Google Workspace only sends SCIM to a handful of apps in its own catalog, and Cloudflare does not document SCIM support for Google Workspace at all; Access re-checks group membership every sign-in instead. Leave the email claim and OIDC Claims fields empty. Click "Save".`,
'Cloudflare then shows a link. Open it signed in as the Google Workspace super admin and approve it, so Access can read group membership.',
`Back in Integrations → Identity providers, click "Test" next to Google Workspace. It should show your name and your groups${group ? `, including ${group}; add yourself to that group first (the Google Group step shows how), or the test cannot show it` : ''}. Then run this command again.`,
],
Expand Down Expand Up @@ -204,7 +211,7 @@ function sessionLabel(duration) {
function includeRule(app) {
if (app.allow.googleGroup) {
const domain = app.allow.googleGroup.split('@')[1];
return `In the policy, add one Include rule: choose the selector "Google Workspace groups" and enter ${app.allow.googleGroup}. That selector is offered only once Google Workspace is a login method; if it is missing, the Google Workspace step was not done. Until it is, choose "Emails" instead and enter the @${domain} address of each person who needs in now; this command replaces that rule with the group once Google Workspace is connected.`;
return `In the policy, add one Include rule: choose the selector "Google Groups" (an older Cloudflare UI calls this "Google Workspace groups") and enter ${app.allow.googleGroup}. That selector is offered only once Google Workspace is a login method; if it is missing, the Google Workspace step was not done. Until it is, choose "Emails" instead and enter the @${domain} address of each person who needs in now; this command replaces that rule with the group once Google Workspace is connected. Then click "+ Add require (AND)" and add a second condition, selector "Emails ending in", value @${domain}, as defence in depth.`;
}
if (app.allow.emailDomain)
return `In the policy, add one Include rule: choose the selector "Emails ending in" and enter @${app.allow.emailDomain}.`;
Expand Down Expand Up @@ -239,17 +246,20 @@ export function accessAppGuide(app, zone) {
const policy = `${app.name} allow`;
const audienceSteps = [
`In Cloudflare One, go to Access controls → Applications and click "Configure" on "${app.name}".`,
`Open the "Additional settings" tab, copy "Application Audience (AUD) Tag", and paste it at this command's prompt. It is 64 lowercase letters and digits, and it is the value ${app.audienceSecret} holds.`,
'On the "Additional settings" tab, under "Cookie settings", turn on "Enable Binding Cookie" if it is off. Leave "HTTP Only" on and "SameSite" set to "Lax".',
`Still on "Additional settings", copy "Application Audience (AUD) Tag", and paste it at this command's prompt. It is 64 lowercase letters and digits, and it is the value ${app.audienceSecret} holds.`,
];
const createSteps = [
`Open Cloudflare One and choose the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). Go to Access controls → Applications. If "${app.name}" is already listed, it exists: skip the steps that create it.`,
`Open Cloudflare One and choose the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). Go to Access controls → Applications. If "${app.name}" is already listed, it exists: skip the steps that create it. ${ACCOUNT_NAME_CHECK}`,
'Click "Create new application" at the top right (some screens say "Add an application"). An account with no applications yet shows only a list of prerequisites; the button is still at the top right.',
'In the "Add an application" dialog, under "Self-hosted and private", choose the "Public DNS" tab. Do not choose "Private destinations", "Workers", or "Service auth". Click "Continue with Self-hosted and private". The page is now "Create new self-hosted application"; work down it from the top.',
'Under "Destinations" there should be public hostname rows. If you see a "Private IPs" row with "Private IP address" and "Port" instead, "Private destinations" was chosen: click "+ Add public hostname", then remove the empty private row, or go back and choose "Public DNS".',
`Add one public hostname row per address with "+ Add public hostname", leaving any other box empty: ${hostnames.join('; ')}. A path does not cover the paths under it, and a wildcard does not cover its parent, so every row is needed; with one missing, that part of the site would be open to anyone.`,
`Add one public hostname row per address with "+ Add public hostname", leaving any other box empty: ${hostnames.join('; ')}. A path does not cover the paths under it, and a wildcard does not cover its parent, so every row is needed; with one missing, that part of the site would be open to anyone. Match each row's Subdomain box exactly, including a row that says "Subdomain empty" — the Subdomain box starts empty regardless of what a row calls for, and leaving it empty where a row names one puts the whole domain behind sign-in instead of only the part named.`,
'Leave "Allow access through browser-based RDP, SSH, or VNC sessions" off.',
`Under "Access policies", which says "No policy associated", open "Add current policies". If a policy named ${policy} is listed, choose it and go on to "Authentication". Otherwise click "Create new policy", name it exactly ${policy}, and set the action to "Allow".`,
`Under "Access policies", which says "No policy associated", open "Add current policies". If a policy named ${policy} is listed, choose it and go on to "Authentication". Otherwise click "Create new policy", name it exactly ${policy}, set the action to "Allow", and leave "Policy session duration" at its default, "Same as application session duration".`,
includeRule(app),
'Do not add a Country rule: it would lock out anyone who signs in while travelling, for little real protection, since the email or group rule above already limits who gets in.',
'Leave "Override global multi-factor authentication settings (MFA)" and "Just-in-time access" off. MFA belongs in Google, not a Cloudflare Access rule: a Workspace admin enforces 2-Step Verification in the Google Admin console instead.',
`Save the policy. If it opened in another tab, come back to this page and choose ${policy} in "Add current policies".`,
'Skip "Policy tester".',
...identitySteps(app),
Expand Down Expand Up @@ -282,13 +292,13 @@ export function googleGroupGuide(group, apps) {
url: 'https://admin.google.com/ac/groups',
steps: [
`Do this as a Google Workspace admin with the Groups administrator privilege. The group decides who can sign in to ${names}.`,
`Open the Google Admin console at https://admin.google.com and go to Menu → Directory → Groups. If ${group} is already listed, skip to the step that adds members.`,
`Open the Google Admin console at https://admin.google.com and go to Menu → Directory → Groups. If ${group} is already listed, click it, then "Access settings", and check it before relying on it: "Who can join the group" is "Only invited users", and "Allow external members in the group" is off. Fix either if not, then skip to the step that adds members.`,
'Click "Create group".',
`Group name: ${names}. Group email: type ${local} and keep the domain ${domain}. Description: People who can sign in to ${names}. Group owner(s): add yourself and anyone who will add and remove people later.`,
'Click "Next". Tick "Security", because the group controls access, and click "Next".',
'Set Access type to "Restricted" and "Who can join the group" to "Only invited users". Leave "Allow external members in the group" off. Click "Create Group".',
`Open the group, click "Members", then "Add members". Type each person's @${domain} address, including your own so you can test the sign-in, and click "Add To Group". Only accounts in the ${domain} Workspace can sign in through Access, so a personal Gmail address does not work, even in the group.`,
`Later, to let someone in, open Directory → Groups → ${group} → Members and click "Add members". To take someone out, point to them in the Members list and click "Remove". A removal takes effect at their next sign-in, within ${session}.`,
`Later, to let someone in, open Directory → Groups → ${group} → Members and click "Add members". To take someone out, remove them from ${group} or suspend their Google account: point to them in the Members list and click "Remove", or Menu → Directory → Users → their name → "Suspend user". Either takes effect at their next sign-in, within ${session}. To end their access immediately instead of waiting, also go to Cloudflare One → Team & Resources → Users, find them, and revoke their session.`,
],
};
}
Expand All @@ -297,7 +307,7 @@ export function turnstileGuide(widget, cloudflare, configPath) {
const config = configPath ?? 'the production wrangler config';
const mode = { managed: 'Managed', 'non-interactive': 'Non-interactive', invisible: 'Invisible' };
const createSteps = [
`Open Turnstile in the Cloudflare dashboard with the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). If a widget named "${widget.name}" is already listed, click it and go to the step for the Site Key.`,
`Open Turnstile in the Cloudflare dashboard with the LVBT account (${LVBT_CLOUDFLARE_ACCOUNT}). If a widget named "${widget.name}" is already listed, click it and go to the step for the Site Key. ${ACCOUNT_NAME_CHECK}`,
`Click "Add widget". Widget name: ${widget.name}.`,
`Under "Hostname management", add ${widget.domains.join(', ')}.`,
`Widget Mode: "${mode[widget.mode ?? 'managed']}". Leave pre-clearance off, and click "Create".`,
Expand Down
2 changes: 1 addition & 1 deletion .lvbt/web-platform/packages/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lasvegasfortransit/eslint-config",
"version": "0.4.4",
"version": "0.4.5",
"description": "The ESLint configurations every LVBT repository uses.",
"license": "MIT",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion .lvbt/web-platform/packages/playwright-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lasvegasfortransit/playwright-config",
"version": "0.4.4",
"version": "0.4.5",
"description": "The Playwright configuration every LVBT repository spreads into its own: end-to-end tests under tests/e2e, desktop and mobile projects, traces on failure.",
"license": "MIT",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion .lvbt/web-platform/packages/prettier-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lasvegasfortransit/prettier-config",
"version": "0.4.4",
"version": "0.4.5",
"description": "The Prettier configuration every LVBT repository uses.",
"license": "MIT",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion .lvbt/web-platform/packages/typescript-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lasvegasfortransit/typescript-config",
"version": "0.4.4",
"version": "0.4.5",
"description": "TypeScript configurations every LVBT repository extends.",
"license": "MIT",
"repository": {
Expand Down
Loading
Loading