-
Notifications
You must be signed in to change notification settings - Fork 10
Disable sign-ups using email #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| name: CLI Release | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build ${{ matrix.target }} | ||
| runs-on: ${{ matrix.os }} | ||
| permissions: | ||
| contents: read | ||
| strategy: | ||
| matrix: | ||
| include: | ||
| - target: bun-darwin-arm64 | ||
| os: macos-latest | ||
| artifact: chp-darwin-arm64 | ||
| - target: bun-darwin-x64 | ||
| os: macos-latest | ||
| artifact: chp-darwin-x64 | ||
| - target: bun-linux-x64 | ||
| os: ubuntu-latest | ||
| artifact: chp-linux-x64 | ||
| - target: bun-linux-arm64 | ||
| os: ubuntu-latest | ||
| artifact: chp-linux-arm64 | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@v2 | ||
|
Comment on lines
+28
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify workflow action refs that are not SHA-pinned
set -euo pipefail
rg -n 'uses:\s*[^@]+@([vV].+|main|master)$' .github/workflows/cli-release.ymlRepository: techulus/changes-page Length of output: 314 Pin action references to immutable commit SHAs. Using mutable major version tags in a release workflow is a supply-chain risk. Lines 30, 33, 45, 59, 66, and 71 should be pinned to full commit SHAs:
🧷 Example hardening pattern- - name: Checkout code
- uses: actions/checkout@v6
+ - name: Checkout code
+ uses: actions/checkout@<full_commit_sha> # v6
- - name: Setup Bun
- uses: oven-sh/setup-bun@v2
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@<full_commit_sha> # v2
- - name: Upload artifact
- uses: actions/upload-artifact@v6
+ - name: Upload artifact
+ uses: actions/upload-artifact@<full_commit_sha> # v6🤖 Prompt for AI Agents |
||
|
|
||
| - name: Install dependencies | ||
| run: cd packages/cli && bun install | ||
|
Comment on lines
+30
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: According to Bun's official documentation, the recommended CI command for lockfile-enforced installs is bun ci. This command is equivalent to bun install --frozen-lockfile, installs exact versions from bun.lock, and fails if package.json does not match the lockfile. The docs explicitly state: "For CI/CD environments that want to enforce reproducible builds, use bun ci to fail the build if the package.json is out of sync with the lockfile" and "instead of running bun install, run bun ci". You must commit bun.lock to version control for this to work. Citations:
🏁 Script executed: # Check for .bun-version file
fd -i '\.bun-version$' || echo "No .bun-version file found"
# Check for bun.lock
fd -i 'bun\.lock$'
# Check package.json files for any bun version specification
fd 'package\.json$' -x grep -l "bun" {} \; 2>/dev/null | head -10Repository: techulus/changes-page Length of output: 113 🏁 Script executed: # Search for bun.lock files in the repository
find . -name "bun.lock*" -type f
# Check the packages/cli directory structure and look for package.json and bun.lock
ls -la packages/cli/ 2>/dev/null | head -20
# Check if there's a .bun-version or similar configuration in root or packages/cli
find . -maxdepth 2 -name ".*bun*" -o -name "*bun-version*"
# Check the content of packages/cli/package.json for bun references
cat packages/cli/package.json 2>/dev/null | head -50Repository: techulus/changes-page Length of output: 1086 🏁 Script executed: # Comprehensive search for bun.lock anywhere in repo
find . -type f -name "*bun*" | grep -v node_modules | grep -v ".git"
# Check root directory explicitly
ls -la | grep bun
# Search for any references to bun.lock or bun.ci in workflow files
rg "bun\.lock|bun ci" --type yamlRepository: techulus/changes-page Length of output: 47 🏁 Script executed: # Check if bun.lock exists in root
ls -la bun.lock 2>&1
# Look at the entire file list to see what lockfiles exist
ls -la *.lock 2>&1 || echo "No lockfiles found"
# Check git-tracked files for anything with 'bun' in name
git ls-files | grep -i bun || echo "No bun-related files in git"
# Verify the current workflow file content to understand full context
cat .github/workflows/cli-release.ymlRepository: techulus/changes-page Length of output: 2441 Pin Bun version and ensure lockfile-enforced CI installs. Line 31 installs the latest Bun implicitly, and Line 34 uses
🤖 Prompt for AI Agents |
||
|
|
||
| - name: Build binary | ||
| run: | | ||
| cd packages/cli | ||
| VERSION=$(jq -r .version package.json) | ||
| bun build --compile --target=${{ matrix.target }} --define "__CLI_VERSION__=\"${VERSION}\"" ./src/index.ts --outfile ${{ matrix.artifact }} | ||
|
|
||
| - name: Upload artifact | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| name: ${{ matrix.artifact }} | ||
| path: packages/cli/${{ matrix.artifact }} | ||
|
|
||
| release: | ||
| name: Create Release | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Read version | ||
| id: version | ||
| run: echo "version=$(jq -r .version packages/cli/package.json)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Download all artifacts | ||
| uses: actions/download-artifact@v6 | ||
| with: | ||
| path: artifacts | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: cli-v${{ steps.version.outputs.version }} | ||
| name: CLI v${{ steps.version.outputs.version }} | ||
| generate_release_notes: true | ||
| files: | | ||
| artifacts/chp-darwin-arm64/chp-darwin-arm64 | ||
| artifacts/chp-darwin-x64/chp-darwin-x64 | ||
| artifacts/chp-linux-x64/chp-linux-x64 | ||
| artifacts/chp-linux-arm64/chp-linux-arm64 | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,12 @@ import Head from "next/head"; | |||||||||||||||||||||||||
| import Image from "next/image"; | ||||||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||||||
| import { useRouter } from "next/router"; | ||||||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||
| createToastWrapper, | ||||||||||||||||||||||||||
| notifyError, | ||||||||||||||||||||||||||
| notifySuccess, | ||||||||||||||||||||||||||
| } from "../components/core/toast.component"; | ||||||||||||||||||||||||||
| import { DEFAULT_TITLE, SUBTITLE } from "../data/marketing.data"; | ||||||||||||||||||||||||||
| import { ROUTES } from "../data/routes.data"; | ||||||||||||||||||||||||||
| import logoImage from "../public/images/logo.png"; | ||||||||||||||||||||||||||
|
|
@@ -19,6 +25,39 @@ export default function Login() { | |||||||||||||||||||||||||
| const { supabase } = useUserData(); | ||||||||||||||||||||||||||
| const prefersColorScheme = usePrefersColorScheme(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const [email, setEmail] = useState(""); | ||||||||||||||||||||||||||
| const [sending, setSending] = useState(false); | ||||||||||||||||||||||||||
| const [submitted, setSubmitted] = useState(false); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const redirectTo = | ||||||||||||||||||||||||||
| getAppBaseURL() + | ||||||||||||||||||||||||||
| ROUTES.LOGIN_CALLBACK + | ||||||||||||||||||||||||||
| (redirectedFrom ? `?redirectedFrom=${redirectedFrom}` : ""); | ||||||||||||||||||||||||||
|
Comment on lines
+32
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Build Current string concatenation does not normalize Suggested fix- const redirectTo =
- getAppBaseURL() +
- ROUTES.LOGIN_CALLBACK +
- (redirectedFrom ? `?redirectedFrom=${redirectedFrom}` : "");
+ const redirectedFromValue = Array.isArray(redirectedFrom)
+ ? redirectedFrom[0]
+ : redirectedFrom;
+ const redirectUrl = new URL(ROUTES.LOGIN_CALLBACK, getAppBaseURL());
+ if (redirectedFromValue) {
+ redirectUrl.searchParams.set("redirectedFrom", redirectedFromValue);
+ }
+ const redirectTo = redirectUrl.toString();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| async function handleEmailLogin(e: React.FormEvent) { | ||||||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||||||
| if (!email || sending) return; | ||||||||||||||||||||||||||
| setSending(true); | ||||||||||||||||||||||||||
| const { error } = await supabase.auth.signInWithOtp({ | ||||||||||||||||||||||||||
| email, | ||||||||||||||||||||||||||
| options: { shouldCreateUser: false, emailRedirectTo: redirectTo }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| setSending(false); | ||||||||||||||||||||||||||
| if (error) { | ||||||||||||||||||||||||||
| const msg = error.message.toLowerCase(); | ||||||||||||||||||||||||||
| if (msg.includes("signups not allowed") || msg.includes("not found")) { | ||||||||||||||||||||||||||
| notifyError( | ||||||||||||||||||||||||||
| "No account found for this email. Sign up with Google or GitHub." | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| notifyError(error.message); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| setSubmitted(true); | ||||||||||||||||||||||||||
| notifySuccess("Check your email for the login link."); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+37
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden OTP submit flow: always clear loading and avoid account-enumeration messaging. Two issues here:
Suggested fix async function handleEmailLogin(e: React.FormEvent) {
e.preventDefault();
- if (!email || sending) return;
- setSending(true);
- const { error } = await supabase.auth.signInWithOtp({
- email,
- options: { shouldCreateUser: false, emailRedirectTo: redirectTo },
- });
- setSending(false);
- if (error) {
- const msg = error.message.toLowerCase();
- if (msg.includes("signups not allowed") || msg.includes("not found")) {
- notifyError(
- "No account found for this email. Sign up with Google or GitHub."
- );
- } else {
- notifyError(error.message);
- }
- return;
- }
- setSubmitted(true);
- notifySuccess("Check your email for the login link.");
+ const normalizedEmail = email.trim();
+ if (!normalizedEmail || sending) return;
+
+ setSending(true);
+ try {
+ const { error } = await supabase.auth.signInWithOtp({
+ email: normalizedEmail,
+ options: { shouldCreateUser: false, emailRedirectTo: redirectTo },
+ });
+
+ const msg = error?.message.toLowerCase() ?? "";
+ const isNotFound =
+ msg.includes("signups not allowed") || msg.includes("not found");
+
+ if (error && !isNotFound) {
+ notifyError(error.message);
+ return;
+ }
+
+ setSubmitted(true);
+ notifySuccess("If an account exists for this email, you'll receive a login link.");
+ } catch {
+ notifyError("Unable to send login link. Please try again.");
+ } finally {
+ setSending(false);
+ }
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const fontFamily = | ||||||||||||||||||||||||||
| "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -53,6 +92,8 @@ export default function Login() { | |||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| {createToastWrapper(prefersColorScheme)} | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| <div className="min-h-screen flex flex-col items-center justify-center bg-gray-50 dark:bg-gray-800 py-12 px-4 sm:px-6 lg:px-8 overflow-y-auto"> | ||||||||||||||||||||||||||
| <div className="max-w-md w-full space-y-4"> | ||||||||||||||||||||||||||
| <div className="flex flex-col items-center justify-center mb-8"> | ||||||||||||||||||||||||||
|
|
@@ -111,15 +152,45 @@ export default function Login() { | |||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||
| providers={["github", "google"]} | ||||||||||||||||||||||||||
| redirectTo={ | ||||||||||||||||||||||||||
| getAppBaseURL() + | ||||||||||||||||||||||||||
| ROUTES.LOGIN_CALLBACK + | ||||||||||||||||||||||||||
| (redirectedFrom ? `?redirectedFrom=${redirectedFrom}` : "") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| redirectTo={redirectTo} | ||||||||||||||||||||||||||
| theme={prefersColorScheme === "dark" ? "dark" : "light"} | ||||||||||||||||||||||||||
| view="magic_link" | ||||||||||||||||||||||||||
| showLinks={false} | ||||||||||||||||||||||||||
| onlyThirdPartyProviders | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| <div className="relative my-4"> | ||||||||||||||||||||||||||
| <div className="absolute inset-0 flex items-center" aria-hidden="true"> | ||||||||||||||||||||||||||
| <div className="w-full border-t border-gray-300 dark:border-gray-700" /> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| <div className="relative flex justify-center text-sm"> | ||||||||||||||||||||||||||
| <span className="bg-gray-50 dark:bg-gray-800 px-2 text-gray-500 dark:text-gray-400"> | ||||||||||||||||||||||||||
| Or sign in with email | ||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| <form onSubmit={handleEmailLogin} className="space-y-3"> | ||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||
| type="email" | ||||||||||||||||||||||||||
| required | ||||||||||||||||||||||||||
| autoComplete="email" | ||||||||||||||||||||||||||
| placeholder="you@example.com" | ||||||||||||||||||||||||||
| value={email} | ||||||||||||||||||||||||||
| onChange={(e) => setEmail(e.target.value)} | ||||||||||||||||||||||||||
| disabled={sending || submitted} | ||||||||||||||||||||||||||
| className="block w-full rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 disabled:opacity-60" | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||
| type="submit" | ||||||||||||||||||||||||||
| disabled={sending || submitted} | ||||||||||||||||||||||||||
| className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-60" | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| {submitted | ||||||||||||||||||||||||||
| ? "Check your email" | ||||||||||||||||||||||||||
| : sending | ||||||||||||||||||||||||||
| ? "Sending…" | ||||||||||||||||||||||||||
| : "Send magic link"} | ||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restrict release execution to
mainto avoid accidental off-branch releases.workflow_dispatchcan be run on arbitrary refs. Without a branch guard, Line 3 and Line 48 can release from non-main commits.🔒 Proposed guard
Also applies to: 48-51
🤖 Prompt for AI Agents