Skip to content

Feat/new UI#153

Merged
steveiliop56 merged 29 commits intomainfrom
feat/new-ui
May 20, 2025
Merged

Feat/new UI#153
steveiliop56 merged 29 commits intomainfrom
feat/new-ui

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented May 14, 2025

Summary by CodeRabbit

  • New Features

    • Added support for customizing the login page background image via configuration, environment variables, and app context.
    • Introduced a modernized frontend UI with Tailwind CSS, Radix UI, and custom components including forms, cards, buttons, language selector, OTP inputs, and notifications.
    • Added a unified error page with clearer, localized error messages.
  • Bug Fixes

    • Improved redirect validation and security for login and continue flows with stricter hostname and protocol checks.
    • Refined error and notification handling on login, logout, TOTP, and unauthorized pages.
  • Refactor

    • Replaced Mantine UI components with custom and Radix UI components across the frontend.
    • Updated routing and layout structure using React Router’s createBrowserRouter and centralized error handling.
    • Streamlined app and user context fetching with schema validation and simplified hooks.
    • Simplified form handling using react-hook-form and zod schemas.
  • Chores

    • Updated dependencies, configuration files, and build scripts including Vite and TypeScript path aliases.
    • Improved localization files with new and refined translation keys, removing obsolete entries.
    • Removed obsolete files, workflows, and unused utility functions.
  • Style

    • Introduced a comprehensive Tailwind CSS theme supporting light and dark modes with custom utilities.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces a new background image configuration option across backend and frontend, enabling login page background customization. The frontend is extensively refactored, replacing Mantine with custom and Radix UI components, integrating Tailwind CSS, and restructuring forms, layouts, and notifications. Utility, schema, and configuration files are updated or added to support these changes. Routing and error handling are improved, and localization strings are enhanced. Several obsolete files and components are removed.

Changes

File(s) / Path(s) Change Summary
.env.example, cmd/root.go, internal/types/config.go, internal/types/api.go, internal/handlers/handlers.go, frontend/src/schemas/app-context-schema.ts, frontend/context/app-context.tsx, frontend/components/layout/layout.tsx Added support for configurable background image in backend and frontend, propagated via env var, config structs, API context, and frontend context.
frontend/package.json, frontend/vite.config.ts, frontend/tsconfig.json, frontend/tsconfig.app.json, frontend/Dockerfile.dev, Dockerfile Replaced Mantine with Radix UI and Tailwind CSS; updated dependencies, plugins, build steps, and path aliases accordingly.
frontend/index.html, frontend/public/site.webmanifest Updated favicon links, manifest content, and enabled dark mode styling.
frontend/.prettierignore Expanded Prettier ignore list to include bun.lock, package.json, and localization directories.
frontend/postcss.config.cjs Deleted PostCSS config removing Mantine-specific CSS processing.
frontend/components.json Added new UI component configuration file defining style presets, Tailwind integration, and path aliases.
frontend/src/index.css Replaced with Tailwind-based CSS theme including dark mode, typography, and utility classes.
frontend/src/lib/utils.ts, frontend/src/lib/hooks/use-is-mounted.ts Added utility functions for class names, URL validation, capitalization; removed JSDoc from hook.
frontend/src/components/ui/button.tsx, card.tsx, form.tsx, input.tsx, label.tsx, oauth-button.tsx, select.tsx, separator.tsx, input-otp.tsx, sonner.tsx Added custom UI components replacing Mantine equivalents, including buttons, cards, forms, inputs, labels, OAuth buttons, selects, separators, OTP inputs, and toast notifications.
frontend/src/components/auth/login-form.tsx, totp-form.tsx, language/language.tsx Added/refactored authentication forms and language selector using new UI primitives and React Hook Form.
frontend/src/components/layout/layout.tsx Added new layout component supporting background image and language selector.
frontend/src/components/icons/generic.tsx, google.tsx Renamed and added icon components for OAuth providers.
frontend/src/context/app-context.tsx, user-context.tsx Refactored context providers to use updated schema/type names and simplified query keys.
frontend/src/schemas/app-context-schema.ts, login-schema.ts, user-context-schema.ts, totp-schema.ts Updated and added schema definitions with renamed type aliases.
frontend/src/lib/i18n/i18n.ts, locales/en-US.json, locales/en.json Simplified i18n backend setup and enhanced English localization strings for clarity and consistency.
frontend/src/main.tsx Refactored routing to use createBrowserRouter with centralized error handling; replaced Mantine providers with new layout and toast components.
frontend/src/pages/login-page.tsx, continue-page.tsx, forgot-password-page.tsx, logout-page.tsx, not-found-page.tsx, unauthorized-page.tsx, totp-page.tsx, error-page.tsx Refactored pages to use new UI components, updated control flow, improved error handling, and replaced Mantine notifications with toast notifications.
frontend/src/App.tsx Simplified login/logout redirection logic.
README.md Updated documentation for new UI, usage notes, and acknowledgements.
frontend/src/utils/utils.ts, frontend/src/components/auth/login-forn.tsx, frontend/src/components/auth/oauth-buttons.tsx, frontend/src/components/language-selector/language-selector.tsx, frontend/src/components/layouts/layout.tsx, frontend/src/icons/google.tsx, frontend/src/pages/internal-server-error.tsx, .github/workflows/translations.yml Removed obsolete utility, component, icon, and workflow files.
frontend/eslint.config.js Added ESLint plugin for Tanstack Query and enabled exhaustive dependencies rule.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Backend

    User->>Frontend: Visit login page
    Frontend->>Backend: GET /api/app
    Backend-->>Frontend: { backgroundImage, ... }
    Frontend->>Frontend: Apply background image from context
    User->>Frontend: Submit login form
    Frontend->>Backend: POST /api/login
    Backend-->>Frontend: Login result (success, TOTP required, or error)
    Frontend->>User: Show notifications or redirect accordingly
Loading

Poem

🐇
A background now with flair and hue,
Tailwind winds and Radix grew.
Cards and forms, a toast that sings,
Language swaps and custom things.
Out with Mantine, in with style—
This bunny hops the extra mile!
🌈✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between d59f354 and 97b0d3e.

📒 Files selected for processing (24)
  • frontend/.prettierignore (1 hunks)
  • frontend/components.json (1 hunks)
  • frontend/public/site.webmanifest (1 hunks)
  • frontend/src/components/ui/card.tsx (1 hunks)
  • frontend/src/components/ui/form.tsx (1 hunks)
  • frontend/src/components/ui/input-otp.tsx (1 hunks)
  • frontend/src/components/ui/input.tsx (1 hunks)
  • frontend/src/components/ui/label.tsx (1 hunks)
  • frontend/src/components/ui/sonner.tsx (1 hunks)
  • frontend/src/context/app-context.tsx (1 hunks)
  • frontend/src/context/user-context.tsx (1 hunks)
  • frontend/src/lib/hooks/use-is-mounted.ts (1 hunks)
  • frontend/src/lib/i18n/i18n.ts (1 hunks)
  • frontend/src/lib/i18n/locales.ts (1 hunks)
  • frontend/src/lib/i18n/locales/en-US.json (3 hunks)
  • frontend/src/lib/i18n/locales/en.json (3 hunks)
  • frontend/src/lib/utils.ts (1 hunks)
  • frontend/src/pages/login-page.tsx (1 hunks)
  • frontend/src/schemas/app-context-schema.ts (1 hunks)
  • frontend/src/schemas/login-schema.ts (1 hunks)
  • frontend/src/schemas/totp-schema.ts (1 hunks)
  • frontend/src/schemas/user-context-schema.ts (1 hunks)
  • frontend/vite.config.ts (2 hunks)
  • internal/types/config.go (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
Comment thread frontend/src/pages/continue-page.tsx Fixed
@steveiliop56
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

♻️ Duplicate comments (3)
frontend/src/pages/continue-page.tsx (2)

97-100: Address CodeQL warning: Untrusted URL redirect (similar to line 37, 65).

Similar client-side URL redirect warning from CodeQL. While DOMPurify helps with XSS, you may need additional URL validation safeguards.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 99-99: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.


120-123: Address CodeQL warning: Untrusted URL redirect (similar to line 37, 65, 99).

Similar client-side URL redirect warning from CodeQL.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 122-122: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.

frontend/src/components/ui/select.tsx (1)

51-84: Same readability concern for SelectContent className.

Similar to SelectTrigger, this component has a very long className string that would benefit from extraction.

🧹 Nitpick comments (33)
frontend/src/schemas/totp-schema.ts (1)

3-5: Consider adding validation constraints for the TOTP code.

The current schema accepts any string as a valid TOTP code. Since TOTP codes typically follow specific formats (usually 6-digit numeric codes), consider adding validation constraints.

- export const totpSchema = z.object({
-   code: z.string(),
- });
+ export const totpSchema = z.object({
+   code: z.string().regex(/^\d{6}$/, { message: "TOTP code must be 6 digits" }),
+ });
frontend/public/site.webmanifest (1)

1-21: Good PWA manifest configuration with proper icons and theming.

The web app manifest is well-structured and includes appropriate branding for Tinyauth. The "maskable" purpose for icons is a nice addition for better display on different platforms.

Consider adding more PWA metadata fields for a complete experience:

{
  "name": "Tinyauth",
  "short_name": "Tinyauth",
+ "description": "Secure authentication service",
+ "start_url": "/",
  "icons": [
    {
      "src": "/web-app-manifest-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/web-app-manifest-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "theme_color": "#171717",
  "background_color": "#171717",
  "display": "standalone"
}
frontend/components.json (1)

1-21: Well-configured shadcn/ui setup

The configuration properly sets up shadcn/ui with Tailwind CSS, appropriate path aliases, and the Lucide icon library. This provides a solid foundation for the UI component system.

Two suggestions to consider:

  1. If you're using a custom Tailwind config file, specify its path in the "config" field (currently empty)
  2. Consider using a more specific baseColor than "neutral" if your application has a distinct brand color
frontend/src/pages/forgot-password-page.tsx (2)

7-13: Consider consistent import ordering.

While the functionality is correct, consider grouping related imports together. For example, external library imports (react-i18next, react-markdown) could be grouped separately from internal component imports.

import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { useAppContext } from "@/context/app-context";
+import { useTranslation } from "react-i18next";
import Markdown from "react-markdown";
-import { useTranslation } from "react-i18next";

16-23: Card structure looks good, but consider adding more accessibility attributes.

The card component structure is well-organized. For enhanced accessibility, consider adding appropriate ARIA attributes or roles.

-    <Card className="min-w-xs sm:min-w-sm">
+    <Card className="min-w-xs sm:min-w-sm" role="region" aria-labelledby="forgot-password-title">
      <CardHeader>
-        <CardTitle className="text-3xl">{t("forgotPasswordTitle")}</CardTitle>
+        <CardTitle id="forgot-password-title" className="text-3xl">{t("forgotPasswordTitle")}</CardTitle>
        <CardDescription>
          <Markdown>{forgotPasswordMessage}</Markdown>
        </CardDescription>
      </CardHeader>
    </Card>
frontend/src/components/language/language.tsx (1)

22-33: Consider adding a more descriptive aria-label for accessibility.

The current implementation could benefit from improved accessibility for screen readers.

-    <Select onValueChange={handleSelect} value={language}>
-      <SelectTrigger className="absolute top-5 right-5">
+    <Select onValueChange={handleSelect} value={language} aria-label="Change application language">
+      <SelectTrigger className="absolute top-5 right-5" aria-label="Select language">
        <SelectValue placeholder="Select language" />
      </SelectTrigger>
      <SelectContent>
        {Object.entries(languages).map(([key, value]) => (
-          <SelectItem key={key} value={key}>
+          <SelectItem key={key} value={key} aria-label={value}>
            {value}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
README.md (1)

64-65: Fix typo in acknowledgements.

There's a spelling error in "bacgkround" that should be corrected.

- Kurt Cotoaga for providing the bacgkround image of the app.
+ Kurt Cotoaga for providing the background image of the app.
frontend/src/components/layout/layout.tsx (1)

11-11: Remove empty img element.

The empty <img> tag serves no purpose and should be removed. Empty img tags can cause accessibility issues and unnecessary DOM nodes.

-      <img></img>
frontend/src/components/ui/input.tsx (1)

10-14: Consider adding spacing between classNames for better readability

While the functionality is perfect, the long className string could be split across multiple lines for better readability and maintenance.

-        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+        [
+          "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
+          "dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent",
+          "px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none",
+          "file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium",
+          "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+        ].join(" "),
frontend/src/components/ui/oauth-button.tsx (1)

13-33: Consider adding spacing between icon and text

The implementation of the OAuth button looks good overall. The loading state is handled well with the spinner animation. However, there's no spacing between the icon and the button text, which might make them appear too close together.

          {icon}
-          {title}
+          <span className="ml-2">{title}</span>
frontend/src/components/ui/separator.tsx (1)

28-36: Typo in component name.

There's a typo in the component name SeperatorWithChildren - it should be SeparatorWithChildren (missing 'a').

While the component functionality is correct, the naming inconsistency could cause confusion for other developers.

-function SeperatorWithChildren({ children }: { children: React.ReactNode }) {
+function SeparatorWithChildren({ children }: { children: React.ReactNode }) {

And also update the export:

-export { Separator, SeperatorWithChildren };
+export { Separator, SeparatorWithChildren };
frontend/src/components/auth/login-form.tsx (3)

25-27: Consider providing explicit defaultValues to avoid uncontrolled-to-controlled warnings

react-hook-form will complain (in strict mode) if any field value changes from undefinedstring.
Supplying explicit empty strings prevents the warning and documents the expected shape of the form.

 const form = useForm<LoginSchema>({
   resolver: zodResolver(loginSchema),
+  defaultValues: { username: "", password: "" },
 });

55-71: Clickable “Forgot password” link may overlap the input and break pointer events

Because the link is absolutely positioned inside a relatively-positioned div that also contains the input, it can overlap the input’s bottom-right area, stealing focus or hovering tool-tips.
A quick fix is to use pointer-events-none on the parent and re-enable them on the anchor, or move the link outside the input container.

-<a
-  href="/forgot-password"
-  className="text-muted-foreground text-sm absolute right-0 bottom-10"
->
+<a
+  href="/forgot-password"
+  className="text-muted-foreground text-sm absolute right-0 -bottom-6"
+>

The negative bottom offset keeps the link visually close while staying outside the input box’s hit-test rectangle.


75-77: Disable the submit button while loading to prevent double submits

loading is forwarded to the Button, but unless the component itself internally sets disabled, users can still click repeatedly.
Defensively set the attribute:

-<Button className="w-full" type="submit" loading={loading}>
+<Button
+  className="w-full"
+  type="submit"
+  loading={loading}
+  disabled={loading}
+>
frontend/src/pages/unauthorized-page.tsx (1)

55-57: Navigate call loses original params – consider preserving intended redirect

Users who reach /unauthorized often expect to retry login and be redirected back.
You can forward the current search params:

-<Button onClick={() => navigate("/login")}>
+<Button onClick={() => navigate(`/login${window.location.search}`)}>

or store the intended URL in state.

frontend/src/main.tsx (1)

75-78: Toaster placed inside Layout may render beneath modals or portals

If Layout applies overflow-hidden or transforms, the toast portal can be clipped.
Consider mounting Toaster directly under QueryClientProvider (sibling to Layout) so it sits at the document root.

No code change required if your Layout doesn’t clip overflow; just double-check.

frontend/package.json (2)

30-33: React 19 & React-DOM 19 are pre-release; expect ecosystem churn

Locking to "^19.0.0" will pull the first stable 19.x once released, but today resolves to the latest prerelease.
Third-party libraries (including react-router, react-i18next, etc.) may not yet declare compatibility.

If you don’t rely on React 19 features, consider sticking to "^18.3.0" until the ecosystem stabilises.


18-19: @tailwindcss/vite has been deprecated in favour of the official tailwindcss plugin

The recommended approach with Vite ≥4:

npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

and add tailwindcss to postcss.config.js, not via a Vite plugin.

Confirm that @tailwindcss/vite still meets your needs or migrate to the standard setup to avoid maintenance issues.

frontend/src/pages/logout-page.tsx (1)

35-37: Remove unnecessary async keyword inside setTimeout callback

The callback does not contain any await statements (and setTimeout ignores returned promises).
Dropping async avoids an unused promise and a marginally smaller bundle.

-      setTimeout(async () => {
+      setTimeout(() => {
         window.location.replace("/login");
       }, 500);
frontend/src/pages/login-page.tsx (1)

80-85: Use status code, not message string, to detect rate-limit errors

String-matching the error message is brittle and locale-dependent. Leverage Axios’ typed response instead:

-description: error.message.includes("429")
-  ? t("loginFailRateLimit")
-  : t("loginFailSubtitle"),
+description:
+  axios.isAxiosError(error) && error.response?.status === 429
+    ? t("loginFailRateLimit")
+    : t("loginFailSubtitle"),
frontend/src/components/auth/totp-form.tsx (1)

21-23: Provide defaultValues to avoid uncontrolled-to-controlled warning

React-Hook-Form will complain if a field starts uncontrolled. Supplying an initial empty string is sufficient:

-const form = useForm<TotpSchema>({
-  resolver: zodResolver(totpSchema),
-});
+const form = useForm<TotpSchema>({
+  resolver: zodResolver(totpSchema),
+  defaultValues: {
+    code: "",
+  },
+});
frontend/src/pages/totp-page.tsx (1)

35-38: Avoid orphaned timers & use the router’s replace flag

  1. setTimeout inside onSuccess won’t be cleared if the component unmounts within 500 ms (e.g. route change triggered elsewhere), leaving a dangling callback.
  2. Consider using replace: true to avoid an extra entry in the browser history when you automatically redirect.
-onSuccess: () => {
+onSuccess: () => {
   toast.success(t("totpSuccessTitle"), {
     description: t("totpSuccessSubtitle"),
   });

-  setTimeout(() => {
-    navigate(`/continue?redirect_uri=${redirectUri}`);
-  }, 500);
+  const timer = setTimeout(() => {
+    navigate(`/continue?redirect_uri=${redirectUri}`, { replace: true });
+  }, 500);
+
+  return () => clearTimeout(timer);
 },
frontend/src/components/ui/card.tsx (2)

5-16: Expose ref by using React.forwardRef

Most UI primitives forward refs so callers can focus a card or measure its size.
Wrapping the root component with forwardRef costs almost nothing and keeps the API flexible.

-function Card({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
+const Card = React.forwardRef<HTMLDivElement, React.ComponentProps<"div">>(
+  ({ className, ...props }, ref) => (
+    <div
+      ref={ref}
       data-slot="card"
       className={cn(
         "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
         className
       )}
       {...props}
     />
-  )
-}
+  )
+);
+Card.displayName = "Card";

Apply the same pattern to the other sub-components for consistency.


31-37: Use semantic heading element for titles

Screen-reader users benefit from proper heading structure. Swapping the div for an h2 (or the appropriate level) inside CardTitle preserves styling while improving accessibility.

-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+function CardTitle({ className, ...props }: React.ComponentProps<"h2">) {
   return (
-    <div
+    <h2
       data-slot="card-title"
       className={cn("leading-none font-semibold", className)}
       {...props}
-    />
+    />
   )
 }
frontend/src/components/ui/input-otp.tsx (2)

52-54: Consider extracting long className into a variable

The className string is quite long, which can make maintenance more difficult. Consider extracting it to a constant or using a style composition utility.

-      className={cn(
-        "data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
-        className
-      )}
+      className={cn(inputOTPSlotStyles, className)}

And at the top of the file:

const inputOTPSlotStyles = "data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]";

67-73: Consider adding explicit styling to InputOTPSeparator

Unlike the other components, the separator doesn't have explicit styling classes. Consider adding specific styling for consistent appearance and better control over the separator's look.

function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
  return (
-    <div data-slot="input-otp-separator" role="separator" {...props}>
+    <div 
+      data-slot="input-otp-separator" 
+      role="separator" 
+      className={cn("flex items-center justify-center mx-1", props.className)}
+      {...props}
+    >
      <MinusIcon />
    </div>
  )
}
frontend/src/index.css (2)

4-4: Consider using standard Tailwind dark mode approach

The custom variant @custom-variant dark (&:is(.dark *)) might be less maintainable than Tailwind's built-in dark mode. Tailwind's approach would be to use dark: prefix in class names with the darkMode: 'class' in your Tailwind config.

Consider aligning with Tailwind's standard approach for better compatibility and maintainability.


122-176: Consider component-based styling rather than global element styling

While styling HTML elements directly (h1, h2, p, etc.) works, it can lead to unexpected styling in third-party components. Consider using utility classes or component-specific styling instead for better isolation.

Rather than styling global elements like h1, consider creating utility classes like .heading-1 that can be applied only where needed.

frontend/src/components/ui/button.tsx (2)

8-10: Consider extracting long base className into a variable

Similar to other components, the base className string is quite long, which can make maintenance more difficult.

+const buttonBaseStyles = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive";
+
-const buttonVariants = cva(
-  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+const buttonVariants = cva(
+  buttonBaseStyles,

55-66: Consider simplifying the conditional rendering

The conditional rendering for the loading state could be simplified using the props pattern rather than separate return statements.

function Button({
  className,
  variant,
  size,
  asChild = false,
  loading = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean;
    loading?: boolean;
  }) {
  const Comp = asChild ? Slot : "button";

-  if (loading) {
-    return (
-      <Comp
-        data-slot="button"
-        className={cn(buttonVariants({ variant, size, className }))}
-        disabled
-        {...props}
-      >
-        <Loader2 className="animate-spin" />
-      </Comp>
-    );
-  }
-
  return (
    <Comp
      data-slot="button"
      className={cn(buttonVariants({ variant, size, className }))}
+      disabled={loading || props.disabled}
      {...props}
+    >
+      {loading ? <Loader2 className="animate-spin" /> : props.children}
-    />
+    </Comp>
  );
}
frontend/src/lib/i18n/locales/en-US.json (1)

1-53: Consider organizing locale keys by feature area

The locale file is growing and mixes different concerns. Consider organizing keys by feature area (login, errors, notifications, etc.) using nested objects for better maintainability.

{
-    "loginTitle": "Welcome back, login with",
-    "loginTitleSimple": "Welcome back, please login",
-    "loginDivider": "Or",
-    ...
+    "login": {
+        "title": "Welcome back, login with",
+        "titleSimple": "Welcome back, please login",
+        "divider": "Or",
+        ...
+    },
+    "errors": {
+        "title": "An error occurred",
+        "subtitle": "An error occured while trying to perform this action...",
+        ...
+    },
frontend/src/components/ui/form.tsx (1)

50-52: Consider using optional chaining for context checks.

The code throws an error if the field context is missing, which is good for development but could be handled more gracefully.

-  if (!fieldContext) {
+  if (!fieldContext?.name) {
     throw new Error("useFormField should be used within <FormField>")
   }
frontend/src/components/ui/select.tsx (1)

25-49: Consider extracting the className into a separate variable for readability.

The SelectTrigger component has a very long className string that makes the code harder to read and maintain.

function SelectTrigger({
  className,
  size = "default",
  children,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
  size?: "sm" | "default";
}) {
+  const baseClassName = cn(
+    "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-secondary dark:hover:bg-secondary/80 flex w-fit items-center justify-between gap-2 rounded-md border bg-primary px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+    className,
+  );
  
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      data-size={size}
-      className={cn(
-        "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-secondary dark:hover:bg-secondary/80 flex w-fit items-center justify-between gap-2 rounded-md border bg-primary px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className,
-      )}
+      className={baseClassName}
      {...props}
    >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 854c10d and 10dc228.

⛔ Files ignored due to path filters (18)
  • assets/logo-rounded.png is excluded by !**/*.png
  • assets/logo-solid.jpg is excluded by !**/*.jpg
  • assets/logo.png is excluded by !**/*.png
  • assets/screenshot.png is excluded by !**/*.png
  • frontend/bun.lock is excluded by !**/*.lock
  • frontend/bun.lockb is excluded by !**/bun.lockb
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/public/android-chrome-192x192.png is excluded by !**/*.png
  • frontend/public/android-chrome-512x512.png is excluded by !**/*.png
  • frontend/public/apple-touch-icon.png is excluded by !**/*.png
  • frontend/public/background.jpg is excluded by !**/*.jpg
  • frontend/public/favicon-16x16.png is excluded by !**/*.png
  • frontend/public/favicon-32x32.png is excluded by !**/*.png
  • frontend/public/favicon-96x96.png is excluded by !**/*.png
  • frontend/public/favicon.ico is excluded by !**/*.ico
  • frontend/public/favicon.svg is excluded by !**/*.svg
  • frontend/public/web-app-manifest-192x192.png is excluded by !**/*.png
  • frontend/public/web-app-manifest-512x512.png is excluded by !**/*.png
📒 Files selected for processing (62)
  • .env.example (1 hunks)
  • README.md (3 hunks)
  • cmd/root.go (3 hunks)
  • frontend/.prettierignore (1 hunks)
  • frontend/Dockerfile.dev (1 hunks)
  • frontend/components.json (1 hunks)
  • frontend/eslint.config.js (2 hunks)
  • frontend/index.html (1 hunks)
  • frontend/package.json (2 hunks)
  • frontend/postcss.config.cjs (0 hunks)
  • frontend/public/site.webmanifest (1 hunks)
  • frontend/src/App.tsx (1 hunks)
  • frontend/src/components/auth/login-form.tsx (1 hunks)
  • frontend/src/components/auth/login-forn.tsx (0 hunks)
  • frontend/src/components/auth/oauth-buttons.tsx (0 hunks)
  • frontend/src/components/auth/totp-form.tsx (1 hunks)
  • frontend/src/components/icons/generic.tsx (1 hunks)
  • frontend/src/components/icons/google.tsx (1 hunks)
  • frontend/src/components/language-selector/language-selector.tsx (0 hunks)
  • frontend/src/components/language/language.tsx (1 hunks)
  • frontend/src/components/layout/layout.tsx (1 hunks)
  • frontend/src/components/layouts/layout.tsx (0 hunks)
  • frontend/src/components/ui/button.tsx (1 hunks)
  • frontend/src/components/ui/card.tsx (1 hunks)
  • frontend/src/components/ui/form.tsx (1 hunks)
  • frontend/src/components/ui/input-otp.tsx (1 hunks)
  • frontend/src/components/ui/input.tsx (1 hunks)
  • frontend/src/components/ui/label.tsx (1 hunks)
  • frontend/src/components/ui/oauth-button.tsx (1 hunks)
  • frontend/src/components/ui/select.tsx (1 hunks)
  • frontend/src/components/ui/separator.tsx (1 hunks)
  • frontend/src/components/ui/sonner.tsx (1 hunks)
  • frontend/src/context/app-context.tsx (1 hunks)
  • frontend/src/context/user-context.tsx (1 hunks)
  • frontend/src/icons/google.tsx (0 hunks)
  • frontend/src/index.css (1 hunks)
  • frontend/src/lib/hooks/use-is-mounted.ts (1 hunks)
  • frontend/src/lib/i18n/i18n.ts (1 hunks)
  • frontend/src/lib/i18n/locales/en-US.json (3 hunks)
  • frontend/src/lib/i18n/locales/en.json (3 hunks)
  • frontend/src/lib/utils.ts (1 hunks)
  • frontend/src/main.tsx (1 hunks)
  • frontend/src/pages/continue-page.tsx (1 hunks)
  • frontend/src/pages/error-page.tsx (1 hunks)
  • frontend/src/pages/forgot-password-page.tsx (1 hunks)
  • frontend/src/pages/internal-server-error.tsx (0 hunks)
  • frontend/src/pages/login-page.tsx (1 hunks)
  • frontend/src/pages/logout-page.tsx (2 hunks)
  • frontend/src/pages/not-found-page.tsx (1 hunks)
  • frontend/src/pages/totp-page.tsx (1 hunks)
  • frontend/src/pages/unauthorized-page.tsx (1 hunks)
  • frontend/src/schemas/app-context-schema.ts (1 hunks)
  • frontend/src/schemas/login-schema.ts (1 hunks)
  • frontend/src/schemas/totp-schema.ts (1 hunks)
  • frontend/src/schemas/user-context-schema.ts (1 hunks)
  • frontend/src/utils/utils.ts (0 hunks)
  • frontend/tsconfig.app.json (1 hunks)
  • frontend/tsconfig.json (1 hunks)
  • frontend/vite.config.ts (2 hunks)
  • internal/handlers/handlers.go (3 hunks)
  • internal/types/api.go (1 hunks)
  • internal/types/config.go (2 hunks)
💤 Files with no reviewable changes (8)
  • frontend/src/pages/internal-server-error.tsx
  • frontend/src/components/language-selector/language-selector.tsx
  • frontend/postcss.config.cjs
  • frontend/src/components/auth/login-forn.tsx
  • frontend/src/components/layouts/layout.tsx
  • frontend/src/components/auth/oauth-buttons.tsx
  • frontend/src/utils/utils.ts
  • frontend/src/icons/google.tsx
🧰 Additional context used
🧬 Code Graph Analysis (20)
frontend/src/components/ui/label.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/input.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
internal/handlers/handlers.go (1)
internal/types/config.go (1)
  • Config (4-37)
frontend/src/pages/forgot-password-page.tsx (2)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/components/ui/card.tsx (4)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
frontend/src/components/ui/separator.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/layout/layout.tsx (2)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/components/language/language.tsx (1)
  • LanguageSelector (12-35)
frontend/src/context/app-context.tsx (2)
internal/types/api.go (1)
  • AppContext (42-52)
frontend/src/schemas/app-context-schema.ts (1)
  • AppContextSchema (14-14)
frontend/src/pages/not-found-page.tsx (2)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/components/language/language.tsx (2)
frontend/src/lib/i18n/locales.ts (2)
  • SupportedLanguage (34-34)
  • languages (1-32)
frontend/src/components/ui/select.tsx (5)
  • Select (173-173)
  • SelectTrigger (181-181)
  • SelectValue (182-182)
  • SelectContent (174-174)
  • SelectItem (176-176)
frontend/src/context/user-context.tsx (2)
internal/types/types.go (1)
  • UserContext (41-47)
frontend/src/schemas/user-context-schema.ts (1)
  • UserContextSchema (13-13)
frontend/src/components/auth/login-form.tsx (4)
frontend/src/schemas/login-schema.ts (2)
  • LoginSchema (8-8)
  • loginSchema (3-6)
frontend/src/components/ui/form.tsx (6)
  • Form (158-158)
  • FormField (164-164)
  • FormItem (159-159)
  • FormLabel (160-160)
  • FormControl (161-161)
  • FormMessage (163-163)
frontend/src/components/ui/input.tsx (1)
  • Input (21-21)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/pages/logout-page.tsx (4)
frontend/src/context/user-context.tsx (1)
  • useUserContext (25-35)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/lib/utils.ts (1)
  • capitalize (17-19)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/pages/unauthorized-page.tsx (2)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/pages/login-page.tsx (8)
frontend/src/context/user-context.tsx (1)
  • useUserContext (25-35)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/lib/hooks/use-is-mounted.ts (1)
  • useIsMounted (3-15)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardContent (91-91)
frontend/src/components/ui/oauth-button.tsx (1)
  • OAuthButton (13-33)
frontend/src/components/icons/google.tsx (1)
  • GoogleIcon (3-30)
frontend/src/components/ui/separator.tsx (1)
  • SeperatorWithChildren (38-38)
frontend/src/components/auth/login-form.tsx (1)
  • LoginForm (21-81)
frontend/src/components/ui/form.tsx (2)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/label.tsx (1)
  • Label (22-22)
frontend/src/components/ui/card.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/pages/continue-page.tsx (5)
frontend/src/context/user-context.tsx (1)
  • useUserContext (25-35)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/lib/utils.ts (1)
  • isValidUrl (8-15)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/components/ui/select.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/button.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/input-otp.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
🪛 Biome (1.9.4)
frontend/tsconfig.app.json

[error] 3-3: Expected a property but instead found '// Resolve paths'.

Expected a property here.

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 7-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🪛 LanguageTool
README.md

[uncategorized] ~18-~18: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... docker apps. It is designed for traefik but it can be extended to work with other r...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~26-~26: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...inyauth is intended for homelab use only and it is not made for production use cases...

(COMMA_COMPOUND_SENTENCE)


[typographical] ~26-~26: It seems that a comma is missing.
Context: ...e looking for something production ready please use authentik...

(IF_PLEASE_COMMA)


[style] ~38-~38: Consider removing “of” to be more concise
Context: ...ou can find documentation and guides on all of the available configuration of tinyauth in ...

(ALL_OF_THE)


[uncategorized] ~38-~38: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tion and guides on all of the available configuration of tinyauth in the [website](https://ti...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~38-~38: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ...the available configuration of tinyauth in the website. #...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)

🪛 markdownlint-cli2 (0.17.2)
README.md

24-24: Blank line inside blockquote
null

(MD028, no-blanks-blockquote)

🪛 GitHub Check: CodeQL
frontend/src/pages/continue-page.tsx

[warning] 37-37: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.


[warning] 65-65: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.


[warning] 99-99: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.


[warning] 122-122: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.

🔇 Additional comments (86)
frontend/.prettierignore (1)

2-3: Build directory changes look good

The updates correctly reflect the new build output directories used in the frontend project, aligning with modern frontend tooling conventions.

internal/types/api.go (1)

51-51: Good addition of BackgroundImage field

The addition of the BackgroundImage field in the AppContext struct properly implements the new configurable login page background feature. The field is correctly tagged for JSON serialization.

frontend/src/components/icons/generic.tsx (1)

3-3: Function rename from OAuthIcon to GenericIcon is appropriate

The renaming makes the component more semantically accurate and better reflects its general-purpose nature, improving reusability across different contexts.

frontend/src/schemas/app-context-schema.ts (2)

3-12: Schema structure improvements look good

The updated schema format is cleaner and more consistent. The addition of oauthAutoRedirect and backgroundImage fields properly supports the new features introduced in this PR.


14-14: Type naming convention improvement

Renaming from AppContextSchemaType to AppContextSchema removes redundancy and aligns with modern TypeScript naming conventions.

frontend/Dockerfile.dev (1)

6-6: Correct lockfile filename used: The Dockerfile now correctly copies bun.lock instead of the deprecated bun.lockb, ensuring deterministic installs and proper cache utilization.

frontend/src/schemas/login-schema.ts (2)

4-5: Consistent indentation improved: The username and password fields are now indented with 4 spaces, aligning with project conventions and improving readability.


8-8:

Details

✅ Verification successful

Type alias renamed for clarity: Exported type updated to LoginSchema, matching the loginSchema object. Ensure all references have been updated.


🏁 Script executed:

#!/bin/bash
# Verify no remaining references to `LoginFormValues`
rg -l 'LoginFormValues' -n .

Length of output: 28


All references to LoginFormValues have been removed
Ran rg -l 'LoginFormValues' -n . and confirmed no matches in the codebase. The LoginSchema alias is up to date and no further changes are needed.

frontend/index.html (2)

14-14:

Details

❓ Verification inconclusive

Enable dark mode styling: Added class="dark" on <body> to activate Tailwind CSS dark mode (class strategy).


🏁 Script executed:

#!/bin/bash
# Confirm Tailwind config uses class-based dark mode
rg -n 'darkMode' tailwind.config.*

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Locate any Tailwind config files in the repo
fd 'tailwind.config.*' .
# If found, search for darkMode setting within them
fd 'tailwind.config.*' . | xargs -r rg -n 'darkMode'

Length of output: 79


Verify Tailwind dark mode configuration

I couldn’t find a tailwind.config.js (or .cjs/.ts) in the repo, so it’s unclear whether dark mode is set to the class strategy. Please confirm that your Tailwind config includes:

// tailwind.config.js
module.exports = {
  darkMode: 'class',
  // …
}

• If you haven’t added a config file yet, create one at the project root with the above setting.
• If it exists under a nonstandard name or location, ensure the build is picking it up.
• Without darkMode: 'class', <body class="dark"> won’t activate the intended styling.


10-11:

Details

✅ Verification successful

Manifest and Apple Mobile Config updated: Changed the web manifest link to /site.webmanifest and added the Apple mobile web app title.


🏁 Script executed:

#!/bin/bash
# Verify `site.webmanifest` presence in public directory
rg -l 'site.webmanifest' public/

Length of output: 80


🏁 Script executed:

#!/bin/bash
# Locate any .webmanifest files in the repo
fd '*.webmanifest'

# Search for references to site.webmanifest
rg -l 'site.webmanifest'

Length of output: 358


🏁 Script executed:

#!/bin/bash
# Locate any .webmanifest files in the repository
find . -type f -name '*.webmanifest'

Length of output: 73


Verified manifest file presence
The site.webmanifest is present at frontend/public/site.webmanifest, so the <link rel="manifest" href="/site.webmanifest" /> reference in frontend/index.html is valid. Approving these changes.

frontend/tsconfig.app.json (1)

3-7: Add path alias for cleaner imports: Introducing "baseUrl": "." and "paths": { "@/*": ["./src/*"] } simplifies module resolution across the app. Note: tsconfig files support JSONC comments (//), so the inline comment is valid.

🧰 Tools
🪛 Biome (1.9.4)

[error] 3-3: Expected a property but instead found '// Resolve paths'.

Expected a property here.

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 7-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

frontend/tsconfig.json (1)

7-12: Enable path alias in root TS config: Added compilerOptions.baseUrl and paths to support @/* imports and maintain consistency with tsconfig.app.json and vite.config.ts.

frontend/eslint.config.js (1)

6-6: Good addition of TanStack Query linting

Adding the TanStack Query ESLint plugin is a great enhancement for ensuring proper usage of React Query hooks throughout the codebase. Setting the exhaustive-deps rule to "error" will help prevent common mistakes in dependency arrays when using query and mutation hooks.

Also applies to: 20-20, 28-28

.env.example (1)

30-32: LGTM: Well-documented new environment variables

The new environment variables for forgot password message, OAuth auto-redirect, and background image are clearly documented with appropriate example values. The none default for OAUTH_AUTO_REDIRECT is a safe choice that won't unexpectedly redirect users.

frontend/src/schemas/user-context-schema.ts (2)

4-11: Clean schema refactoring with preparation for future fields

The code formatting is improved, and the commented placeholder fields for name and email clearly indicate plans for future implementation without affecting current functionality.


13-13: Type naming consistency improvement

Renaming from UserContextSchemaType to UserContextSchema follows better naming conventions and matches the pattern typically used with Zod schemas.

frontend/vite.config.ts (3)

1-4: Appropriate plugin updates for new UI framework

Switching from @vitejs/plugin-react-swc to @vitejs/plugin-react and adding the Tailwind plugin aligns with the UI framework migration mentioned in the summary.


8-13: Good addition of path aliasing

Adding path aliases with @ pointing to the src directory will improve import readability throughout the codebase and reduce relative path complexity.


22-23: Verify security implications of allowedHosts setting

Setting allowedHosts: true allows connections from any host to the development server. This is convenient for development but could have security implications if accidentally used in production.

This setting is likely fine for development purposes, but please ensure that production deployments use appropriate host restrictions if needed.

frontend/src/lib/hooks/use-is-mounted.ts (1)

1-15: Well-implemented React hook for managing component mount state.

This custom hook is a good solution for preventing state updates on unmounted components, which helps avoid memory leaks and React warnings. The implementation follows React best practices by using:

  • useRef for a mutable value that persists between renders
  • useEffect for mount/unmount lifecycle management
  • useCallback to maintain function reference stability
frontend/src/pages/error-page.tsx (1)

9-20: Clean and well-structured error page component.

The ErrorPage component is well-implemented with proper internationalization support and responsive design. It follows modern React patterns and integrates well with the new UI component system.

internal/types/config.go (2)

36-36: Well-structured addition of BackgroundImage to Config

The addition of the BackgroundImage field with appropriate validation and mapstructure tags follows the established pattern in this configuration struct.


48-48: Consistent propagation of BackgroundImage to handlers configuration

The BackgroundImage field has been correctly added to the HandlersConfig struct, ensuring that the background image URL will be available at the handler level.

frontend/src/App.tsx (1)

11-11: Clean implementation of guest user redirection

Redirecting non-authenticated users to the login page is appropriate and clearly implemented.

frontend/src/components/icons/google.tsx (1)

1-30: Well-implemented Google icon component

The GoogleIcon component follows React best practices by accepting and forwarding SVG props, using accurate brand colors, and providing proper SVG attributes. This will work well for OAuth login buttons.

frontend/src/lib/utils.ts (3)

1-6: Well-structured utility function for class name management.

The cn function elegantly combines Tailwind CSS classes using clsx and tailwind-merge, which helps prevent class conflicts when applying multiple dynamic classes. This is a common pattern in Tailwind CSS projects.


8-15: Robust URL validation approach.

The URL validation implementation using try-catch with the URL constructor is a clean and effective approach. This correctly handles invalid URLs by catching the exception that occurs during URL construction.


17-19: Simple and effective string capitalization function.

The implementation is correct and straightforward, capitalizing only the first character while preserving the rest of the string.

frontend/src/pages/forgot-password-page.tsx (1)

1-6: Clean migration to the new UI component system.

The migration from Mantine UI components to the new card-based UI structure using Radix UI components is well-implemented.

frontend/src/components/language/language.tsx (2)

1-10: Clean imports with clear dependencies.

The imports are well-organized, properly separating external libraries and internal modules.


12-20: Language handling implementation looks good.

The component correctly initializes the state from the i18n instance and properly handles language changes.

README.md (7)

2-2: Good improvement to logo display.

The updated logo size and path appears more appropriate for the README presentation.


18-18: Improved clarity in project description.

The description now more accurately characterizes compatibility with different reverse proxies.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~18-~18: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... docker apps. It is designed for traefik but it can be extended to work with other r...

(COMMA_COMPOUND_SENTENCE)


20-20: More generic alt text for screenshot.

The updated alt text is more appropriate as it describes the content of the image.


26-26: Clearer warning about intended use cases.

The note about homelab use is now more explicit, which helps users understand the project's intended scope.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~26-~26: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...inyauth is intended for homelab use only and it is not made for production use cases...

(COMMA_COMPOUND_SENTENCE)


[typographical] ~26-~26: It seems that a comma is missing.
Context: ...e looking for something production ready please use authentik...

(IF_PLEASE_COMMA)


30-30: Expanded Discord server description.

Adding details about the Discord server's focus on "self-hosting and homelabbing" provides clearer expectations for users.


38-38: Simplified documentation wording.

The documentation reference is now more concise.

🧰 Tools
🪛 LanguageTool

[style] ~38-~38: Consider removing “of” to be more concise
Context: ...ou can find documentation and guides on all of the available configuration of tinyauth in ...

(ALL_OF_THE)


[uncategorized] ~38-~38: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tion and guides on all of the available configuration of tinyauth in the [website](https://ti...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~38-~38: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ...the available configuration of tinyauth in the website. #...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)


56-56: Visual improvement to sponsors section.

The new horizontal inline list of linked GitHub avatar images provides a more visually appealing way to recognize sponsors.

frontend/src/lib/i18n/i18n.ts (1)

9-11: Good simplification of the i18n backend configuration.

Switching to resourcesToBackend with dynamic imports makes the code cleaner while maintaining functionality. This approach bundles localization files with the application, which can improve loading reliability but might slightly increase the initial bundle size compared to on-demand loading.

frontend/src/components/ui/sonner.tsx (1)

4-21: Well-implemented theme-aware toast component.

The Toaster component nicely wraps Sonner with theme support, correctly passing props and applying CSS variables for consistent theming. Good use of TypeScript casting to ensure type safety.

frontend/src/components/ui/label.tsx (1)

6-20: Good implementation of the Label component.

The component effectively wraps Radix UI's label primitive with appropriate styling and accessibility features. The use of the cn utility function for class name merging is a good practice, and the component correctly handles all props passed to it.

frontend/src/components/ui/input.tsx (1)

5-19: Well-implemented input component with good accessibility support

The Input component is well-structured with comprehensive Tailwind styling that handles various states including focus, disabled, and validation states. The component follows best practices by:

  • Using composition via props spreading
  • Supporting customization through className prop
  • Including proper accessibility styling for invalid states
  • Supporting both light and dark modes
internal/handlers/handlers.go (2)

230-230: Good update to redirect unauthenticated users to login page

The change from the previous redirect destination to the login page is appropriate for unauthenticated users, and using http.StatusTemporaryRedirect (307) is the correct choice for authentication redirects.


451-451:

Details

✅ Verification successful

Background image integration looks good

The addition of the background image to the app context allows the frontend to access the configured background image URL. This aligns well with the UI changes in the frontend.


🏁 Script executed:

#!/bin/bash
# Description: Verify the background image is properly validated or has a default value

# Check if there's validation for the background image URL
rg -A 3 -B 3 "BackgroundImage" internal/types/config.go

Length of output: 571


Background image configuration is validated
The BackgroundImage field in internal/types/config.go carries a validate:"required" tag, ensuring it must be provided at startup. No further defaulting or validation is needed.

frontend/src/components/ui/oauth-button.tsx (1)

6-11: Well-defined props interface with proper typing

The props interface extends the Button component's props and adds specific properties needed for OAuth buttons. This is a good approach for component composition.

frontend/src/context/app-context.tsx (3)

1-6: Good type update for app context

Updating the context type to use AppContextSchema from a centralized schema file is a good practice for type consistency across the application.


13-16: Simplified query implementation

The query has been simplified by using a more concise query key and a Promise chain. This is a good refactoring that maintains functionality while improving code readability.


28-28: Improved null check

Using !context instead of an explicit comparison handles both null and undefined cases, which is more robust.

frontend/src/context/user-context.tsx (6)

1-2: Well-structured import simplification.

The imports have been simplified to only include what's needed, and the type name has been updated from UserContextSchemaType to UserContextSchema to match the schema definition.


6-6: Type definition updated correctly.

The context type has been updated to use the renamed UserContextSchema type, maintaining type safety.


13-15: Query simplification and improved data handling.

The query has been improved by:

  1. Simplifying the query key from ["userContext"] to ["user"]
  2. Using a cleaner promise chain in the query function
  3. Destructuring with better variable names (isFetching instead of isLoading)

This makes the code more readable and maintainable.


18-18: Error handling condition updated correctly.

The error handling condition now uses isFetching instead of isLoading, maintaining consistency with the variable names above.


22-22: Provider value updated to match variable naming.

The context provider now correctly passes data rather than userContext, consistent with the variable naming in the hook.


28-31: Improved error message and null check.

The null check has been changed to a falsy check (!context), and the error message has been slightly reworded to be more specific about where the hook must be used.

frontend/src/pages/not-found-page.tsx (5)

1-8: UI library migration from Mantine to custom components.

The imports have been updated to use the new custom UI components instead of Mantine, aligning with the project's UI framework migration.


10-10: Added React Router navigation import.

Correctly imported useNavigate from React Router to implement client-side navigation.


14-15: React Router hook initialization.

Added the useNavigate hook to enable client-side navigation.


17-25: UI refactoring with new component structure.

The UI has been successfully refactored to use the new component structure with appropriate styling:

  1. Using Card as the main container with responsive min-width
  2. Using semantic components like CardHeader, CardTitle, etc.
  3. Setting up proper flexbox layout for the button container
  4. Using Tailwind classes for styling instead of component props

The refactoring maintains the same functionality while updating to the new design system.


23-23: Improved navigation with React Router.

Changed from window.location.replace("/") to React Router's navigate("/"), which provides a smoother client-side navigation experience without full page reloads.

frontend/src/components/ui/separator.tsx (2)

1-6: Well-structured imports and client directive.

The file properly starts with the "use client" directive and includes necessary imports for Radix UI's separator primitive and the utility function for class name merging.


8-26: Well-implemented Separator component.

The Separator component is correctly implemented:

  1. It wraps Radix UI's separator primitive
  2. Provides sensible defaults for orientation and decorative props
  3. Uses the cn utility function to merge classes
  4. Adds appropriate data attributes and styling

The component is reusable and accessible, following best practices for UI primitives.

cmd/root.go (3)

94-94: Added BackgroundImage configuration field.

The BackgroundImage field has been correctly added to the HandlersConfig struct to support the new configurable login page background image feature.


202-202: Added command-line flag for background image.

Added a new command-line flag background-image with a sensible default value of /background.jpg. This provides users with an easy way to configure the login page background image.


235-235: Added environment variable binding.

The environment variable BACKGROUND_IMAGE is now correctly bound to the configuration, allowing for configuration through environment variables, which is essential for containerized deployments.

frontend/src/main.tsx (1)

5-5: Verify react-router@7 availability – createBrowserRouter is not exported by v6

Imports come from "react-router". In every stable release up to v6.23, createBrowserRouter & RouterProvider live in react-router-dom.

The package.json lists "react-router": "^7.5.3" – a future-facing version that is not yet published on npm at the time of writing.
Please confirm the package exists or switch to:

-import { createBrowserRouter, RouterProvider } from "react-router";
+import { createBrowserRouter, RouterProvider } from "react-router-dom";
frontend/src/pages/logout-page.tsx (1)

27-33: Nice concise mutation definition

The react-query mutation is clearly expressed and keyed; great use of toast for UX feedback.

frontend/src/pages/totp-page.tsx (1)

16-17: Double-check import source for useNavigate

useNavigate is re-exported from react-router, but the DOM implementation lives in react-router-dom.
Mixing the two packages in the same bundle can bloat size and occasionally cause version-mismatch issues. If the rest of the codebase imports navigation hooks from react-router-dom, align this import for consistency:

-import { useNavigate } from "react-router";
+import { useNavigate } from "react-router-dom";
frontend/src/components/ui/input-otp.tsx (2)

1-6: Good import organization!

The imports are well-organized with external dependencies first, followed by internal utilities, with appropriate spacing for readability.


7-25: LGTM: Well-structured InputOTP component

The component is well-designed with good prop typing and appropriate use of the cn utility for class name composition. The data attributes enhance accessibility.

frontend/src/index.css (3)

44-111: Well-organized color system with OKLCH

The color system is comprehensive and well-organized using the modern OKLCH color space, which provides better perceptual uniformity compared to older color models. The separation of light and dark theme variables is well-structured.


113-120: Good use of Tailwind layers

Using the @layer base directive is a good practice for properly organizing CSS with Tailwind, ensuring your base styles are properly processed in the correct order.


46-77:

Details

❓ Verification inconclusive

Consider adding a11y contrast verification for color combinations

It's important to ensure that color combinations (especially background/foreground pairs) meet WCAG accessibility contrast requirements. Let's verify this.


🏁 Script executed:

#!/bin/bash
# Check for a11y contrast checking tools
echo "To ensure accessibility compliance, verify these color combinations meet WCAG 2.1 AA contrast requirements:"
echo "- Background to Foreground: oklch(1 0 0) to oklch(0.145 0 0)"
echo "- Primary to Primary Foreground: oklch(0.205 0 0) to oklch(0.985 0 0)"
echo "- Secondary to Secondary Foreground: oklch(0.97 0 0) to oklch(0.205 0 0)"
echo "- Muted to Muted Foreground: oklch(0.97 0 0) to oklch(0.556 0 0)"
echo "- Dark mode combinations (similar pairs)"

# Let user know they should check these with an online tool
echo "Use an online OKLCH contrast checker to verify these combinations meet WCAG 2.1 AA standards (4.5:1 for normal text)"

Length of output: 1132


Add WCAG 2.1 AA Contrast Checks for OKLCH Color Variables

Please verify that all background/foreground and semantic color pairs meet at least a 4.5:1 contrast ratio (WCAG 2.1 AA for normal text). At a minimum, check:

  • Background ↔ Foreground:
    --background: oklch(1 0 0)
    --foreground: oklch(0.145 0 0)

  • Primary ↔ Primary‐Foreground:
    --primary: oklch(0.205 0 0)
    --primary-foreground: oklch(0.985 0 0)

  • Secondary ↔ Secondary‐Foreground:
    --secondary: oklch(0.97 0 0)
    --secondary-foreground: oklch(0.205 0 0)

  • Muted ↔ Muted‐Foreground:
    --muted: oklch(0.97 0 0)
    --muted-foreground: oklch(0.556 0 0)

  • Any dark‐mode or inverted‐theme variants you introduce

Recommendations:
• Use a WCAG-compliant contrast checker that supports OKLCH (e.g., OKLab‐aware online tools or design-system scripts).
• If any pair falls below 4.5:1, adjust the lightness (L) channel or swap colors to hit the minimum ratio.

Once validated, consider adding automated tests or documentation in your design tokens to lock in these thresholds.

frontend/src/components/ui/button.tsx (3)

1-7: Good import organization

The imports are well-organized with React and external dependencies first, followed by internal utilities and icons.


8-39: Well-implemented button variants using class-variance-authority

The button variants are well-structured using cva with good default variants and comprehensive styling for different states (hover, focus, disabled). The use of shadow-xs for subtle depth is a nice touch.


41-76: Well-structured Button component with loading state

The Button component is well implemented with proper types, props handling, and a nice loading state that shows a spinner. The use of Radix UI's Slot for composition is a good pattern for flexibility.

frontend/src/lib/i18n/locales/en-US.json (3)

3-3: Good addition of simplified login title

Adding a simplified login title variation is helpful for contexts where you don't want to specifically mention login methods.


22-22: Good HTML tag normalization

Changing from <Code> to lowercase <code> is more consistent with HTML standards, which typically use lowercase tag names.


50-52: Good addition of error messages

The addition of generic error messages and provider fetch errors improves user experience by providing clear feedback for these scenarios.

frontend/src/pages/continue-page.tsx (4)

28-33: LGTM: Improved redirect validation logic.

The validation flow has been improved to immediately redirect to the logout page when the redirect URI is missing or invalid, which is a good security practice.


1-14: Good UI component refactoring.

The imports show a clean transition from Mantine to a custom UI component system with improved component organization.


79-111: Good security practice: HTTP downgrade warning.

Excellent implementation of a protocol downgrade warning when redirecting from HTTPS to HTTP, giving users clear options.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 99-99: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.


46-75: Well-designed warning UI for untrusted domains.

The card-based UI for warning about untrusted domains is clean and user-friendly, presenting clear options with appropriate styling for potentially dangerous actions.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 65-65: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.

frontend/src/components/ui/form.tsx (3)

1-165: Well-structured form component system with strong typings.

This is a well-implemented form component system that leverages React Hook Form and Radix UI primitives. The architecture follows modern React patterns:

  1. Proper context usage for form field state management
  2. Strong TypeScript typing with generics for form values
  3. Accessible components with proper ARIA attributes
  4. Consistent error handling and display

The component composition approach makes it easy to build complex forms while maintaining accessibility and consistent styling.


43-64: Good hook implementation with comprehensive error handling.

The useFormField hook provides a complete API for form fields with proper error handling and context validation. The generation of unique IDs for accessibility is particularly good practice.


105-121: Excellent accessibility implementation.

The FormControl component sets proper ARIA attributes for accessibility, including aria-describedby that dynamically changes based on error state, and aria-invalid for form validation.

frontend/src/components/ui/select.tsx (1)

1-183: Well-implemented Select component with comprehensive styling.

This is a well-structured component that wraps Radix UI's Select primitive with custom styling. The implementation:

  1. Uses data attributes for styling states
  2. Provides proper component composition
  3. Has strong TypeScript typing
  4. Includes proper accessibility attributes

Comment thread frontend/index.html
Comment thread frontend/src/App.tsx
Comment thread frontend/src/components/language/language.tsx
Comment thread frontend/src/components/layout/layout.tsx
Comment thread internal/handlers/handlers.go
Comment thread frontend/src/lib/i18n/locales/en-US.json Outdated
Comment thread frontend/src/pages/continue-page.tsx Outdated
Comment thread frontend/src/pages/continue-page.tsx
Comment thread frontend/src/pages/continue-page.tsx
Comment thread frontend/src/components/ui/select.tsx
@steveiliop56 steveiliop56 marked this pull request as ready for review May 14, 2025 18:00
@steveiliop56
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
frontend/src/pages/unauthorized-page.tsx (2)

13-18: Improved: Now using React Router hooks for location access

The component now properly uses useLocation() instead of directly accessing window.location, addressing the previous review comments. This approach is reactive and compatible with SSR.


27-27: Fixed: Corrected the misspelled i18n key

The typo in the i18n key has been fixed from "unaothorizedLoginSubtitle" to "unauthorizedLoginSubtitle", addressing the previous review comment.

🧹 Nitpick comments (1)
frontend/src/pages/unauthorized-page.tsx (1)

21-23: Consider extracting this guard clause before the main component logic

The early return with <Navigate> inside the component body makes the flow harder to follow. Consider moving this check before setting up other hooks like useNavigate().

export const UnauthorizedPage = () => {
  const { search } = useLocation();
  const searchParams = new URLSearchParams(search);
  const username = searchParams.get("username");
  const resource = searchParams.get("resource");
  const groupErr = searchParams.get("groupErr");

  const { t } = useTranslation();

  if (!username) {
    return <Navigate to="/" />;
  }

  const navigate = useNavigate();
  // Rest of component...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10dc228 and 0020372.

📒 Files selected for processing (17)
  • .env.example (1 hunks)
  • .github/dependabot.yml (1 hunks)
  • cmd/root.go (3 hunks)
  • frontend/package.json (2 hunks)
  • frontend/src/components/layout/layout.tsx (1 hunks)
  • frontend/src/lib/i18n/locales/en-US.json (3 hunks)
  • frontend/src/lib/i18n/locales/en.json (3 hunks)
  • frontend/src/pages/continue-page.tsx (1 hunks)
  • frontend/src/pages/login-page.tsx (1 hunks)
  • frontend/src/pages/logout-page.tsx (2 hunks)
  • frontend/src/pages/totp-page.tsx (1 hunks)
  • frontend/src/pages/unauthorized-page.tsx (1 hunks)
  • frontend/src/schemas/app-context-schema.ts (1 hunks)
  • frontend/src/schemas/user-context-schema.ts (1 hunks)
  • internal/handlers/handlers.go (4 hunks)
  • internal/types/api.go (1 hunks)
  • internal/types/config.go (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .github/dependabot.yml
🚧 Files skipped from review as they are similar to previous changes (14)
  • cmd/root.go
  • frontend/src/schemas/user-context-schema.ts
  • frontend/src/schemas/app-context-schema.ts
  • internal/types/api.go
  • .env.example
  • internal/types/config.go
  • internal/handlers/handlers.go
  • frontend/src/pages/totp-page.tsx
  • frontend/src/pages/login-page.tsx
  • frontend/src/pages/logout-page.tsx
  • frontend/src/lib/i18n/locales/en.json
  • frontend/src/components/layout/layout.tsx
  • frontend/src/pages/continue-page.tsx
  • frontend/src/lib/i18n/locales/en-US.json
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/pages/unauthorized-page.tsx (2)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
🔇 Additional comments (4)
frontend/package.json (3)

2-2: Package name change reflects the new UI framework

The package name change from "frontend" to "tinyauth-shadcn" accurately reflects the UI library transition, making it clear this is now using Shadcn UI (which is built on Radix UI and Tailwind).


42-56: Dev dependencies updated to support new UI framework

The development dependencies have been appropriately updated to support the new UI framework, including TypeScript 5.7.2, ESLint 9, and Vite 6.


13-39:

Details

❌ Incorrect review comment

Caution: React 19 is currently in pre-release status

The dependencies have been thoroughly updated to support the new UI framework, replacing Mantine with Radix UI components and Tailwind CSS. However, React 19 (lines 30-31) is currently in pre-release status and may not be stable for production use.


🌐 Web query:

What is the current release status of React 19.0.0?

💡 Result:

React 19.0.0 is currently in its stable release, having been officially released on December 5, 2024[2][3]. This marks the latest major version of the React JavaScript library, following React 18 which ended its active support when React 19 was released[4].

The stable release came after a release candidate (RC) phase, with React 19.0.0-rc.1 being released on November 14, 2024[10]. Since the initial stable release, React has already moved forward with version 19.1.0, which was released on March 28, 2025[1][4][7].

React 19 brings significant changes and new features, including:

  • Actions for handling asynchronous data fetching[5][9]
  • The useActionState hook for managing action states[5]
  • The useOptimistic hook for optimistic UI updates[5]
  • Support for passing ref as a prop without forwardRef[5]
  • Pre-warming for suspended trees and React DOM static APIs (added since the RC)[2]
  • Improved hydration to better handle third-party scripts and browser extensions[2]
  • Enhanced error reporting with new root options like onCaughtError and onUncaughtError[2][3]

React 19 also includes some breaking changes, such as requiring the new JSX transform, removing propTypes, defaultProps for functions, contextTypes, getChildContext, string refs, and several other deprecated APIs[3]. To help with the transition, the React team published React 18.3.1 with additional deprecation warnings to identify issues before upgrading to React 19[3].

React 19 is also now available in React Native, with React Native 0.78 shipping React 19 support as of February 19, 2025[5][8].

Citations:


Remove outdated pre-release warning for React 19

React 19.0.0 has been officially released as a stable version on December 5, 2024 and is safe for production use. You can remove the caution about React 19 being in pre-release status.

Likely an incorrect or invalid review comment.

frontend/src/pages/unauthorized-page.tsx (1)

38-60: UI successfully migrated to new component architecture

The UI has been successfully migrated from Mantine components to the new Card-based structure, using Tailwind CSS for styling. The new approach is more consistent with the overall UI redesign and provides better separation of concerns between structure and styling.

The navigation has also been improved by using React Router's useNavigate() hook instead of direct window location manipulation.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
README.md (6)

17-17: Add comma before conjunction in compound sentence
“It is designed for traefik, but it can be extended to work with other reverse proxies…” would read more clearly with a comma before “but.”

- It is designed for traefik but it can be extended to work with other reverse proxies like caddy and nginx.
+ It is designed for traefik, but it can be extended to work with other reverse proxies like caddy and nginx.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~17-~17: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... docker apps. It is designed for traefik but it can be extended to work with other r...

(COMMA_COMPOUND_SENTENCE)


19-19: Enhance alt text for accessibility
The alt="Screenshot" is generic. Consider describing the content, e.g., “Tinyauth login screen.”

- ![Screenshot](assets/screenshot.png)
+ ![Tinyauth login screen](assets/screenshot.png)

25-25: Insert commas and hyphenate ‘production-ready’
Add commas for readability and hyphenate “production-ready.”

- Tinyauth is intended for homelab use only and it is not made for production use cases. If you are looking for something production ready please use [authentik](https://goauthentik.io) instead.
+ Tinyauth is intended for homelab use only, and it is not made for production use cases. If you are looking for something production-ready, please use [authentik](https://goauthentik.io) instead.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~25-~25: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...inyauth is intended for homelab use only and it is not made for production use cases...

(COMMA_COMPOUND_SENTENCE)


[typographical] ~25-~25: It seems that a comma is missing.
Context: ...e looking for something production ready please use authentik...

(IF_PLEASE_COMMA)


29-29: Add comma before ‘but’ in second sentence
“It is not only for tinyauth, but general self-hosting and homelabbing.” reads more naturally with a comma.

- It is not only for tinyauth but general self-hosting and homelabbing.
+ It is not only for tinyauth, but general self-hosting and homelabbing.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~29-~29: Possible missing comma found.
Context: ...or tinyauth! It is not only for tinyauth but general self-hosting and homelabbing. [...

(AI_HYDRA_LEO_MISSING_COMMA)


37-37: Refine preposition and pluralize configurations
Use “on” instead of “in” and pluralize “configuration” for clarity.

- You can find documentation and guides on all of the available configuration of tinyauth in the [website](https://tinyauth.app).
+ You can find documentation and guides on all available configurations of tinyauth on the [website](https://tinyauth.app).
🧰 Tools
🪛 LanguageTool

[style] ~37-~37: Consider removing “of” to be more concise
Context: ...ou can find documentation and guides on all of the available configuration of tinyauth in ...

(ALL_OF_THE)


[uncategorized] ~37-~37: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tion and guides on all of the available configuration of tinyauth in the [website](https://ti...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~37-~37: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ...the available configuration of tinyauth in the website. #...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)


63-64: Fix typo in ‘background’
Correct the spelling of “bacgkround” to “background.”

- **Kurt Cotoaga** for providing the bacgkround image of the app.
+ **Kurt Cotoaga** for providing the background image of the app.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 76346fe and d59f354.

📒 Files selected for processing (4)
  • .github/workflows/translations.yml (0 hunks)
  • Dockerfile (1 hunks)
  • README.md (3 hunks)
  • cmd/root.go (3 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/translations.yml
✅ Files skipped from review due to trivial changes (1)
  • Dockerfile
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/root.go
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~17-~17: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... docker apps. It is designed for traefik but it can be extended to work with other r...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~25-~25: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...inyauth is intended for homelab use only and it is not made for production use cases...

(COMMA_COMPOUND_SENTENCE)


[typographical] ~25-~25: It seems that a comma is missing.
Context: ...e looking for something production ready please use authentik...

(IF_PLEASE_COMMA)


[uncategorized] ~29-~29: Possible missing comma found.
Context: ...or tinyauth! It is not only for tinyauth but general self-hosting and homelabbing. [...

(AI_HYDRA_LEO_MISSING_COMMA)


[style] ~37-~37: Consider removing “of” to be more concise
Context: ...ou can find documentation and guides on all of the available configuration of tinyauth in ...

(ALL_OF_THE)


[uncategorized] ~37-~37: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tion and guides on all of the available configuration of tinyauth in the [website](https://ti...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~37-~37: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ...the available configuration of tinyauth in the website. #...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)

🪛 markdownlint-cli2 (0.17.2)
README.md

23-23: Blank line inside blockquote
null

(MD028, no-blanks-blockquote)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
README.md (1)

2-2: Approve new logo markup
The updated <img> tag correctly replaces the logo with the new source and uses a width attribute for sizing.

@steveiliop56
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2025

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🔭 Outside diff range comments (1)
frontend/Dockerfile.dev (1)

1-22: 🛠️ Refactor suggestion

Missing Tailwind configuration file.

Since you've removed the PostCSS configuration file copying and are switching to Tailwind CSS, you should include the Tailwind configuration file in the Docker build.

COPY ./frontend/eslint.config.js ./
COPY ./frontend/index.html ./
COPY ./frontend/tsconfig.json ./
COPY ./frontend/tsconfig.app.json ./
COPY ./frontend/tsconfig.node.json ./
COPY ./frontend/vite.config.ts ./
+COPY ./frontend/tailwind.config.js ./
♻️ Duplicate comments (17)
internal/handlers/handlers.go (2)

181-181: Consider using temporary redirect instead of permanent redirect for unauthorized access

Using http.StatusPermanentRedirect (308) tells browsers to permanently cache this redirect. If a user later gains access to this resource, browsers might continue redirecting without checking the server.

-			c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
+			c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))

228-228: ⚠️ Potential issue

Consider using temporary redirect instead of permanent redirect for group authorization failures

Using http.StatusPermanentRedirect (308) for authorization failures could cause browsers to cache the redirect permanently, which is problematic if a user is later added to the required group.

-				c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
+				c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
frontend/src/components/language/language.tsx (1)

23-23: Ensure the selector has a positioned ancestor.

The absolute positioning on the SelectTrigger requires a positioned ancestor.

frontend/src/context/app-context.tsx (1)

13-16: ⚠️ Potential issue

Add schema validation for API response

The API response is not being validated against the AppContextSchema. Without runtime validation, the application is vulnerable to unexpected data structures.

  const { isFetching, data, error } = useSuspenseQuery({
    queryKey: ["app"],
    queryFn: () => axios.get("/api/app").then((res) => res.data),
  });

  if (error && !isFetching) {
    throw error;
  }

-  return <AppContext.Provider value={data}>{children}</AppContext.Provider>;
+  // Validate incoming data against the Zod schema
+  const validatedData = appContextSchema.parse(data);
+  return <AppContext.Provider value={validatedData}>{children}</AppContext.Provider>;

Be sure to import the schema with:

import { appContextSchema, AppContextSchema } from "@/schemas/app-context-schema";
frontend/src/pages/unauthorized-page.tsx (2)

13-15: Great improvement: Using React Router hooks instead of direct window access

The switch to useLocation() and useNavigate() is a significant improvement over direct window.location access, addressing the issues raised in previous reviews.


27-35: Fixed translation key typo

The previously identified typo in the translation key has been fixed (unauthorizedLoginSubtitle instead of unaothorizedLoginSubtitle).

frontend/src/pages/totp-page.tsx (1)

26-26: Encode redirect_uri immediately after extraction

The redirectUri is fetched from the URL parameters but not immediately encoded, which could lead to query parameter injection vulnerabilities.

-const redirectUri = searchParams.get("redirect_uri");
+const redirectUriRaw = searchParams.get("redirect_uri") ?? "";
+const redirectUri = encodeURIComponent(redirectUriRaw);
frontend/src/components/ui/input-otp.tsx (1)

52-53: Ensure OTP slots adapt to narrow viewports

The OTP slots still use fixed width and height with w-9 h-9, which can cause overflow on very narrow screens. To improve responsiveness:

-"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
+"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex min-w-[2rem] h-9 max-w-[2.25rem] aspect-square items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",

Or use responsive Tailwind classes:

-"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
+"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
frontend/src/pages/login-page.tsx (1)

38-38: 💡 Verification agent

🧩 Analysis chain

Validate redirect_uri to prevent open redirect vulnerabilities

The redirect_uri is used without validation, which could lead to open redirect vulnerabilities. An attacker could craft a malicious URL that redirects users to a phishing site.

-  const redirectUri = searchParams.get("redirect_uri");
+  // Import isValidUrl from utils
+  const rawRedirectUri = searchParams.get("redirect_uri");
+  const redirectUri = rawRedirectUri && isValidUrl(rawRedirectUri) 
+    ? rawRedirectUri 
+    : "/";

🏁 Script executed:

#!/bin/bash
# Look for any URL validation functions in the codebase
echo "Searching for URL validation functions:"
rg "function.*isValid.*Url|isUrlValid|isValidUrl" --type ts

Length of output: 391


Ensure Safe Handling of redirect_uri to Prevent Open Redirects

Please validate and sanitize the redirect_uri before using it. We already have an isValidUrl helper in frontend/src/lib/utils.ts, so you can leverage that alongside a simple same-origin or relative-path check. For example, in frontend/src/pages/login-page.tsx around line 38:

-  const redirectUri = searchParams.get("redirect_uri");
+  import DOMPurify from "dompurify";
+  import { isValidUrl } from "@/lib/utils";
+
+  const rawRedirectUri = searchParams.get("redirect_uri");
+  const sanitizedRedirectUri = rawRedirectUri && DOMPurify.sanitize(rawRedirectUri);
+  // Only allow relative paths (or enforce same-origin) and syntactically valid URLs
+  const redirectUri =
+    sanitizedRedirectUri &&
+    isValidUrl(sanitizedRedirectUri) &&
+    sanitizedRedirectUri.startsWith("/")
+      ? sanitizedRedirectUri
+      : "/";

• Import isValidUrl from @/lib/utils and DOMPurify if not already present.
• Sanitize the raw query-param value.
• Use isValidUrl plus an additional check (e.g., startsWith("/") or comparing new URL().origin to window.location.origin) to enforce same-origin redirects.
• Fallback to / when validation fails.

frontend/src/pages/continue-page.tsx (4)

99-102: Duplicate URL redirect security concern

This is another instance of the same pattern using window.location.href with a sanitized but not fully validated URL.

Apply the same solution as suggested above - replace with a call to a centralized safeRedirect utility function.


122-125: Duplicate URL redirect security concern

This is the third instance of the same pattern using window.location.href with a sanitized but not fully validated URL.

Apply the same solution as suggested above - replace with a call to a centralized safeRedirect utility function.


65-68: 🛠️ Refactor suggestion

Create a centralized safe redirect function

The pattern of sanitizing URLs and directly assigning to window.location.href is repeated multiple times throughout the component (lines 38, 67, 101, 124), creating potential security issues if one instance isn't properly secured.

Extract this logic to a centralized utility function to ensure consistent validation and prevent open redirect vulnerabilities:

// In utils.ts or a new safeRedirect.ts file
export function safeRedirect(rawUrl: string, allowedDomain: string) {
  try {
    const sanitizedUrl = DOMPurify.sanitize(rawUrl);
    const url = new URL(sanitizedUrl);
    if (url.hostname === allowedDomain || url.hostname.endsWith(`.${allowedDomain}`)) {
      window.location.href = sanitizedUrl;
      return true;
    }
    console.warn(`Blocked redirect to non-allowlisted domain: ${url.hostname}`);
    return false;
  } catch (error) {
    console.error("Invalid URL in redirect", error);
    return false;
  }
}

// Then use it in all places:
// if (!safeRedirect(redirectURI, domain)) {
//   navigate("/logout");
// }

37-39: ⚠️ Potential issue

Security concerns still present in disableContinue case

This code path still contains a direct URL redirect without proper validation checks, triggering the CodeQL "Untrusted URL redirect" warning. Even with DOMPurify sanitization (which prevents XSS but not redirect attacks), the code bypasses the hostname verification done in other code paths.

  if (disableContinue) {
-    window.location.href = DOMPurify.sanitize(redirectURI);
+    const sanitizedUri = DOMPurify.sanitize(redirectURI);
+    const url = new URL(sanitizedUri);
+    // Verify domain matches before redirecting
+    if ((url.hostname === domain || url.hostname.endsWith(`.${domain}`))) {
+      window.location.href = sanitizedUri;
+    } else {
+      return <Navigate to="/logout" />;
+    }
  }
frontend/src/components/ui/form.tsx (1)

70-72: Avoid empty object as context default value

Similar to the previous comment about FormFieldContext, the FormItemContext is created with an empty object as the default value.

Apply the same solution - use undefined as the default value and properly check before accessing properties.

frontend/src/components/ui/select.tsx (2)

99-121: Add keyboard navigation tests for SelectItem component

The implementation looks good, but it's important to verify keyboard navigation and screen reader accessibility through testing.

As mentioned in previous review comments, please ensure you have tests that cover keyboard navigation for the Select component, including:

  • Focus management (tabbing into and within the component)
  • Open/close behavior with keyboard (Enter, Space, Escape)
  • Option navigation (Arrow keys, Home/End)
  • Selection with keyboard (Enter/Space)
  • Proper ARIA attributes

107-110: Consider breaking down the long className string for improved readability

Similar to the SelectTrigger component, this component has a very long className string that could be broken down for better maintainability.

Consider applying the same approach suggested earlier to improve code readability.

frontend/index.html (1)

6-8: Missing favicon assets in repository
New favicon references (/favicon-96x96.png, /favicon.svg, /favicon.ico) must be present in your public or static assets directory. Please add these files so the links resolve correctly at runtime.

🧹 Nitpick comments (39)
frontend/src/schemas/app-context-schema.ts (1)

11-11: Consider adding URL validation for the backgroundImage field.

The new backgroundImage field has been added correctly to support the new UI feature. However, since this field likely contains a URL, consider adding validation to ensure it's a valid URL format.

-    backgroundImage: z.string(),
+    backgroundImage: z.string().url().or(z.literal("")),
frontend/src/components/icons/generic.tsx (1)

1-24: Consider adding JSDoc documentation.

While the component implementation is clear, adding a brief JSDoc comment would help other developers understand where and how this icon should be used.

import type { SVGProps } from "react";

+/**
+ * Generic icon used for generic OAuth providers
+ */
export function GenericIcon(props: SVGProps<SVGSVGElement>) {
  // rest of the component
frontend/src/pages/error-page.tsx (1)

9-20: Enhance error page with additional functionality

The error page implementation is clean and follows good practices with proper localization. Consider enhancing it with:

  1. A button to return to the login page or retry the operation
  2. Display of specific error details when available
  3. Error logging functionality
import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";

export const ErrorPage = () => {
  const { t } = useTranslation();
+  const navigate = useNavigate();

  return (
    <Card className="min-w-xs sm:min-w-sm">
      <CardHeader>
        <CardTitle className="text-3xl">{t("errorTitle")}</CardTitle>
        <CardDescription>{t("errorSubtitle")}</CardDescription>
      </CardHeader>
+      <div className="p-6 pt-0 flex justify-end">
+        <Button onClick={() => navigate("/login")}>
+          {t("backToLogin")}
+        </Button>
+      </div>
    </Card>
  );
};
frontend/public/site.webmanifest (1)

1-21: Enhance web app manifest with additional properties

The manifest is well-structured with proper app name, icons, and theme colors that align with your dark mode styling. Consider enhancing it with these additional properties:

{
  "name": "Tinyauth",
  "short_name": "Tinyauth",
+ "start_url": "/",
+ "scope": "/",
  "icons": [
    {
      "src": "/web-app-manifest-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/web-app-manifest-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    },
+    {
+      "src": "/web-app-manifest-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png",
+      "purpose": "any"
+    },
+    {
+      "src": "/web-app-manifest-512x512.png",
+      "sizes": "512x512",
+      "type": "image/png",
+      "purpose": "any"
+    }
  ],
  "theme_color": "#171717",
  "background_color": "#171717",
+ "orientation": "portrait",
  "display": "standalone"
}

The additions include:

  • start_url and scope for better PWA behavior
  • Non-maskable icons (purpose: "any") for browsers that don't support maskable icons
  • orientation preference
frontend/src/lib/utils.ts (1)

17-19: Consider handling edge cases in capitalize function

The capitalize function works for standard strings but doesn't handle edge cases like empty strings or strings that are already capitalized.

 export const capitalize = (str: string) => {
+  if (!str) return str;
   return str.charAt(0).toUpperCase() + str.slice(1);
 }
frontend/src/components/language/language.tsx (3)

13-15: Add type safety for language initialization.

The current code uses a type assertion i18n.language as SupportedLanguage without validation, which assumes i18n.language is always valid.

-  const [language, setLanguage] = useState<SupportedLanguage>(
-    i18n.language as SupportedLanguage,
-  );
+  const [language, setLanguage] = useState<SupportedLanguage>(() => {
+    // Check if current language is supported, fallback to "en-US" if not
+    const currentLang = i18n.language;
+    return Object.keys(languages).includes(currentLang) 
+      ? currentLang as SupportedLanguage 
+      : "en-US";
+  });

17-20: Validate language selection before type casting.

Similar to the initialization issue, you're using type assertions without validation when handling language selection.

-  const handleSelect = (option: string) => {
-    setLanguage(option as SupportedLanguage);
-    i18n.changeLanguage(option as SupportedLanguage);
-  };
+  const handleSelect = (option: string) => {
+    if (Object.keys(languages).includes(option)) {
+      const typedOption = option as SupportedLanguage;
+      setLanguage(typedOption);
+      i18n.changeLanguage(typedOption);
+    }
+  };

22-25: Add accessibility attributes to the language selector.

The language selector lacks accessibility attributes that would help screen reader users understand its purpose.

-    <Select onValueChange={handleSelect} value={language}>
-      <SelectTrigger className="absolute top-5 right-5">
-        <SelectValue placeholder="Select language" />
-      </SelectTrigger>
+    <Select onValueChange={handleSelect} value={language} aria-label="Select language">
+      <SelectTrigger className="absolute top-5 right-5" aria-label="Change application language">
+        <SelectValue placeholder="Select language" />
+      </SelectTrigger>
frontend/src/components/layout/layout.tsx (1)

10-14: Add fallback for missing background image.

The current implementation doesn't handle cases where backgroundImage might be undefined or empty.

-      style={{
-        backgroundImage: `url(${backgroundImage})`,
-        backgroundSize: "cover",
-        backgroundPosition: "center",
-      }}
+      style={{
+        backgroundImage: backgroundImage ? `url(${backgroundImage})` : 'none',
+        backgroundSize: "cover",
+        backgroundPosition: "center",
+      }}
frontend/src/components/ui/sonner.tsx (1)

1-23: Well-implemented Toaster component with proper theme integration!

This component nicely wraps the Sonner toaster library with theme integration from next-themes. The implementation correctly handles theme values and applies custom CSS properties for styling consistency.

Consider adding JSDoc comments to improve documentation:

+/**
+ * A themed wrapper for the Sonner toast notification component
+ * that integrates with the next-themes provider.
+ */
const Toaster = ({ ...props }: ToasterProps) => {
  const { theme = "system" } = useTheme()
frontend/src/components/ui/label.tsx (1)

1-22: Well-structured Label component with comprehensive styling!

This component effectively wraps Radix UI's Label primitive with Tailwind styling, handling all necessary states including disabled states and peer relationships.

Consider adding JSDoc comments to document the component's purpose and usage:

+/**
+ * A styled label component that wraps Radix UI's Label primitive
+ * with Tailwind CSS styling. Supports disabled states and peer relationships.
+ */
function Label({
  className,
  ...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
frontend/src/components/ui/input.tsx (1)

1-21: Comprehensive Input component with excellent state handling!

This component provides a solid foundation for form inputs with extensive styling for various states including focus, disabled, validation, and dark mode.

Consider breaking down the long className string into logical groups for better maintainability:

-      className={cn(
-        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
-        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
-        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
-        className
-      )}
+      className={cn(
+        // Base styles
+        "flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs outline-none transition-[color,box-shadow] md:text-sm",
+        // State styles
+        "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
+        // Focus styles
+        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+        // Validation styles
+        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+        // Theme styles
+        "dark:bg-input/30 selection:bg-primary selection:text-primary-foreground",
+        // Placeholder styles
+        "placeholder:text-muted-foreground",
+        // File input styles
+        "file:text-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium",
+        className
+      )}

Add JSDoc comments to document the component:

+/**
+ * A styled input component that wraps the HTML input element
+ * with comprehensive Tailwind CSS styling for various states.
+ */
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
frontend/src/components/ui/oauth-button.tsx (3)

1-5: Import organization and consistency suggestion

The imports are well-organized but for consistency with other UI components, consider using the cn utility instead of directly using twMerge.

import { Loader2 } from "lucide-react";
import { Button } from "./button";
import React from "react";
-import { twMerge } from "tailwind-merge";
+import { cn } from "@/lib/utils";

6-11: Well-defined props interface with clear structure

The props interface extends Button props and clearly defines additional properties needed for OAuth functionality.

Consider adding JSDoc comments to document the props:

+/**
+ * Props for the OAuthButton component
+ * @property {string} title - The text to display on the button
+ * @property {React.ReactNode} icon - The icon to display next to the text
+ * @property {() => void} [onClick] - Optional click handler
+ * @property {boolean} [loading] - Whether to show a loading spinner
+ */
interface Props extends React.ComponentProps<typeof Button> {
  title: string;
  icon: React.ReactNode;
  onClick?: () => void;
  loading?: boolean;
}

13-33: Enhance the OAuthButton layout and styling

The component correctly handles loading states but could benefit from better spacing between icon and text.

import { Loader2 } from "lucide-react";
import { Button } from "./button";
import React from "react";
-import { twMerge } from "tailwind-merge";
+import { cn } from "@/lib/utils";

interface Props extends React.ComponentProps<typeof Button> {
  title: string;
  icon: React.ReactNode;
  onClick?: () => void;
  loading?: boolean;
}

+/**
+ * OAuth button component that displays an icon and text,
+ * with support for loading state.
+ */
export const OAuthButton = (props: Props) => {
  const { title, icon, onClick, loading, className, ...rest } = props;

  return (
    <Button
      onClick={onClick}
-      className={twMerge("rounded-md", className)}
+      className={cn("rounded-md", className)}
      variant="outline"
      {...rest}
    >
      {loading ? (
        <Loader2 className="animate-spin" />
      ) : (
        <>
-          {icon}
-          {title}
+          <span className="mr-2">{icon}</span>
+          <span>{title}</span>
        </>
      )}
    </Button>
  );
};

This change:

  1. Replaces twMerge with cn for consistency with other UI components
  2. Adds spacing between the icon and text
  3. Wraps elements in spans for better layout control
  4. Adds JSDoc documentation
README.md (4)

64-64: Fix typo in acknowledgement

There's a typo in the background image acknowledgement.

-- **Kurt Cotoaga** for providing the bacgkround image of the app.
+- **Kurt Cotoaga** for providing the background image of the app.

17-17: Consider adding a comma before 'but'

For improved readability and correct grammatical structure, consider adding a comma before 'but' as it connects two independent clauses.

-Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic provider to all of your docker apps. It is designed for traefik but it can be extended to work with other reverse proxies like caddy and nginx.
+Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic provider to all of your docker apps. It is designed for traefik, but it can be extended to work with other reverse proxies like caddy and nginx.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~17-~17: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... docker apps. It is designed for traefik but it can be extended to work with other r...

(COMMA_COMPOUND_SENTENCE)


25-25: Add comma after 'ready'

For better readability, consider adding a comma after "ready" in the production use warning.

-> Tinyauth is intended for homelab use only and it is not made for production use cases. If you are looking for something production ready please use [authentik](https://goauthentik.io) instead.
+> Tinyauth is intended for homelab use only and it is not made for production use cases. If you are looking for something production ready, please use [authentik](https://goauthentik.io) instead.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~25-~25: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...inyauth is intended for homelab use only and it is not made for production use cases...

(COMMA_COMPOUND_SENTENCE)


[typographical] ~25-~25: It seems that a comma is missing.
Context: ...e looking for something production ready please use authentik...

(IF_PLEASE_COMMA)


37-37: Improve documentation sentence phrasing

Consider improving the documentation sentence for better clarity and grammar.

-You can find documentation and guides on all of the available configuration of tinyauth in the [website](https://tinyauth.app).
+You can find documentation and guides on all available configurations of tinyauth on the [website](https://tinyauth.app).
🧰 Tools
🪛 LanguageTool

[style] ~37-~37: Consider removing “of” to be more concise
Context: ...ou can find documentation and guides on all of the available configuration of tinyauth in ...

(ALL_OF_THE)


[uncategorized] ~37-~37: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tion and guides on all of the available configuration of tinyauth in the [website](https://ti...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~37-~37: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ...the available configuration of tinyauth in the website. #...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)

frontend/src/components/ui/separator.tsx (1)

28-36: Typo in component name

There's a spelling mistake in the component name that should be fixed for consistency.

-function SeperatorWithChildren({ children }: { children: React.ReactNode }) {
+function SeparatorWithChildren({ children }: { children: React.ReactNode }) {

Don't forget to update the export statement as well:

-export { Separator, SeperatorWithChildren };
+export { Separator, SeparatorWithChildren };
frontend/src/components/auth/login-form.tsx (1)

65-70: Consider improving the forgot password link positioning

Using absolute positioning with a fixed bottom-10 value may cause layout issues when:

  1. The form has validation errors that change the field's height
  2. The viewport size changes
  3. The text is translated to longer strings

Consider using a more flexible layout approach:

-<a
-  href="/forgot-password"
-  className="text-muted-foreground text-sm absolute right-0 bottom-10"
->
+<div className="flex justify-end mt-1 mb-3">
+  <a
+    href="/forgot-password"
+    className="text-muted-foreground text-sm"
+  >
+    {t("forgotPasswordTitle")}
+  </a>
+</div>
frontend/src/pages/logout-page.tsx (2)

48-50: min-w-xs / min-w-sm are not standard Tailwind utilities.

Unless you extended the Tailwind config with these custom classes, they will be stripped in production builds.
Consider:

-<Card className="min-w-xs sm:min-w-sm">
+<Card className="min-w-[16rem] sm:min-w-[20rem]">

80-83: Button can be disabled instead of custom loading prop (optional).

If the Button component doesn’t natively handle a loading prop, you can rely on the HTML disabled attribute to prevent double submits:

-<Button loading={logoutMutation.isPending}
-        onClick={() => logoutMutation.mutate()}>
+<Button disabled={logoutMutation.isPending}
+        onClick={() => logoutMutation.mutate()}>
frontend/src/main.tsx (2)

20-66: Route table is becoming large – consider extracting to a separate module.

For readability and tree-shaking you could export an array of RouteObjects from routes.tsx and import it here:

// routes.tsx
export const routes: RouteObject[] = [  ]

// main.tsx
const router = createBrowserRouter(routes);

70-82: Toaster placement can live outside Layout (nit).

Placing Toaster under StrictMode but outside layout prevents it from inheriting overflow:hidden or other layout styles that might clip notifications. It also avoids re-mounts if you ever change layouts.

  <Layout>
     <RouterProvider router={router} />
-    <Toaster />
  </Layout>
+ <Toaster />
frontend/src/index.css (1)

113-120: Global outline-ring/50 utility may reduce accessibility.

Adding an outline to every element can cause noisy focus states and unexpected visual artefacts. Prefer to:

*:focus-visible {
  @apply outline-ring/50;
}

so that only keyboard focus is visibly outlined.

frontend/src/pages/totp-page.tsx (1)

32-34: Add accessibility attributes to toast notifications

Toast notifications lack accessibility attributes that would improve screen reader support.

toast.success(t("totpSuccessTitle"), {
  description: t("totpSuccessSubtitle"),
+ aria-live: "polite",
});

// And similarly for the error toast:
toast.error(t("totpFailTitle"), {
  description: t("totpFailSubtitle"),
+ aria-live: "assertive",
});

Also applies to: 43-45

frontend/src/components/auth/totp-form.tsx (1)

34-46: Add autofocus to improve user experience

The first OTP input field should be auto-focused when the component mounts to streamline the user experience.

<InputOTP maxLength={6} disabled={loading} {...field}>
  <InputOTPGroup>
-   <InputOTPSlot index={0} />
+   <InputOTPSlot index={0} autoFocus />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>
frontend/src/components/ui/card.tsx (1)

23-23: Consider adding a comment to explain the complex CSS selector

The complex CSS selector with @container and attribute selectors could benefit from a comment explaining its purpose and behavior.

className={cn(
+  // Uses container queries to create responsive grid layout
+  // and adds conditional styling when card action is present
  "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
  className
)}
frontend/src/components/ui/button.tsx (2)

9-9: Consider moving common styles to the base class

The shadow-xs class is repeated in multiple variant definitions. Consider moving it to the base styles to reduce duplication:

-  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs",

Then remove shadow-xs from each individual variant.


55-66: Prevent layout shifts in loading state

When a button transitions to a loading state, replacing its content with just a spinner can cause layout shifts. Consider preserving the button's width to maintain UI stability:

   if (loading) {
     return (
       <Comp
         data-slot="button"
-        className={cn(buttonVariants({ variant, size, className }))}
+        className={cn(buttonVariants({ variant, size, className }), "min-w-[var(--button-width)]")}
         disabled
+        ref={(el) => {
+          if (el && !el.style.getPropertyValue('--button-width')) {
+            el.style.setProperty('--button-width', `${el.offsetWidth}px`);
+          }
+        }}
         {...props}
       >
         <Loader2 className="animate-spin" />
       </Comp>
     );
   }

Alternatively, show both the spinner and content:

   if (loading) {
     return (
       <Comp
         data-slot="button"
         className={cn(buttonVariants({ variant, size, className }))}
         disabled
         {...props}
       >
         <Loader2 className="animate-spin" />
+        <span className="opacity-50">{props.children}</span>
       </Comp>
     );
   }
frontend/src/lib/i18n/locales/en-US.json (1)

52-52: Fix typo in error message

There's a typo in the error message text:

-    "errorSubtitle": "An error occured while trying to perform this action. Please check the console for more information."
+    "errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information."
frontend/src/pages/continue-page.tsx (1)

46-46: Prefer strict equality for hostname validation

While functional, using loose equality (==) instead of strict equality (===) is generally discouraged in TypeScript/JavaScript for string comparisons to avoid type coercion issues.

-  if (!(url.hostname == domain) && !url.hostname.endsWith(`.${domain}`)) {
+  if (!(url.hostname === domain) && !url.hostname.endsWith(`.${domain}`)) {
frontend/src/components/ui/form.tsx (3)

26-28: Avoid empty object as context default value

Creating a context with an empty object as the default value ({} as FormFieldContextValue) can lead to runtime errors if the context is accidentally used outside its provider. While you do have error handling in the useFormField hook, it's better to provide a more descriptive default.

-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue
-)
+const FormFieldContext = React.createContext<FormFieldContextValue | undefined>(
+  undefined
+)

Then update the useFormField hook to check for undefined:

const useFormField = () => {
  const fieldContext = React.useContext(FormFieldContext)
  const itemContext = React.useContext(FormItemContext)
-  const { getFieldState } = useFormContext()
-  const formState = useFormState({ name: fieldContext.name })
-  const fieldState = getFieldState(fieldContext.name, formState)

  if (!fieldContext) {
    throw new Error("useFormField should be used within <FormField>")
  }

+  const { getFieldState } = useFormContext()
+  const formState = useFormState({ name: fieldContext.name })
+  const fieldState = getFieldState(fieldContext.name, formState)

50-52: Move error check before accessing fieldContext properties

The error check should be performed before trying to access properties from fieldContext to prevent potential null reference errors.

const useFormField = () => {
  const fieldContext = React.useContext(FormFieldContext)
  const itemContext = React.useContext(FormItemContext)
+ 
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>")
+  }
+ 
  const { getFieldState } = useFormContext()
  const formState = useFormState({ name: fieldContext.name })
  const fieldState = getFieldState(fieldContext.name, formState)

-  if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>")
-  }

  const { id } = itemContext

138-139: Add missing error check for formMessageId in FormMessage

The FormMessage component accesses error and formMessageId but doesn't check if they exist before using them, potentially causing runtime errors.

Add a check for the FormMessage component similar to what's done in other form components:

function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
  const { error, formMessageId } = useFormField()
  const body = error ? String(error?.message ?? "") : props.children

  if (!body) {
    return null
  }

  return (
    <p
      data-slot="form-message"
      id={formMessageId}
      className={cn("text-destructive text-sm", className)}
      {...props}
    >
      {body}
    </p>
  )
}

This is a minor issue since useFormField already throws if the context is missing, but it's good to be consistent across components.

frontend/src/components/ui/select.tsx (1)

37-40: Consider breaking down the long className string for improved readability

The extremely long className string makes the code difficult to read and maintain. Consider breaking it down into semantic groups.

  className={cn(
-     "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-card dark:hover:bg-card/90 flex w-fit items-center justify-between gap-2 rounded-md border bg-card hover:bg-card/90 px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+     [
+       // Base styles
+       "flex w-fit items-center justify-between gap-2 rounded-md border",
+       "bg-card hover:bg-card/90 dark:bg-card dark:hover:bg-card/90",
+       "px-3 py-2 text-sm whitespace-nowrap shadow-xs",
+       // Border and focus styles
+       "border-input outline-none transition-[color,box-shadow]",
+       "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+       // Error states
+       "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
+       "dark:aria-invalid:ring-destructive/40",
+       // Disabled state
+       "disabled:cursor-not-allowed disabled:opacity-50",
+       // Text colors
+       "data-[placeholder]:text-muted-foreground",
+       "[&_svg:not([class*='text-'])]:text-muted-foreground",
+       // Sizing variants
+       "data-[size=default]:h-9 data-[size=sm]:h-8",
+       // Child element styling
+       "*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex",
+       "*:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
+       // SVG styling
+       "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+     ].join(" "),
      className,
  )}

Alternatively, you could use an array and join with spaces, or create separate variables for each group of styles.

.env.example (1)

31-32: Add documentation for new environment variables
The new OAUTH_AUTO_REDIRECT and BACKGROUND_IMAGE entries in the example env file align with the backend support, but they should be documented (e.g., in the README or via inline comments) to describe their purpose, accepted values, and default behaviors.

Would you like me to update the project documentation with usage examples for these variables?

frontend/index.html (1)

14-14: Consider dynamic theme toggling
Hardcoding class="dark" forces dark mode for all users. You may want to implement a theme toggle or detect the user's OS preference (prefers-color-scheme) to switch between light and dark modes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 4d061db and d59f354.

⛔ Files ignored due to path filters (18)
  • assets/logo-rounded.png is excluded by !**/*.png
  • assets/logo-solid.jpg is excluded by !**/*.jpg
  • assets/logo.png is excluded by !**/*.png
  • assets/screenshot.png is excluded by !**/*.png
  • frontend/bun.lock is excluded by !**/*.lock
  • frontend/bun.lockb is excluded by !**/bun.lockb
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/public/android-chrome-192x192.png is excluded by !**/*.png
  • frontend/public/android-chrome-512x512.png is excluded by !**/*.png
  • frontend/public/apple-touch-icon.png is excluded by !**/*.png
  • frontend/public/background.jpg is excluded by !**/*.jpg
  • frontend/public/favicon-16x16.png is excluded by !**/*.png
  • frontend/public/favicon-32x32.png is excluded by !**/*.png
  • frontend/public/favicon-96x96.png is excluded by !**/*.png
  • frontend/public/favicon.ico is excluded by !**/*.ico
  • frontend/public/favicon.svg is excluded by !**/*.svg
  • frontend/public/web-app-manifest-192x192.png is excluded by !**/*.png
  • frontend/public/web-app-manifest-512x512.png is excluded by !**/*.png
📒 Files selected for processing (64)
  • .env.example (1 hunks)
  • .github/workflows/translations.yml (0 hunks)
  • Dockerfile (1 hunks)
  • README.md (3 hunks)
  • cmd/root.go (3 hunks)
  • frontend/.prettierignore (1 hunks)
  • frontend/Dockerfile.dev (1 hunks)
  • frontend/components.json (1 hunks)
  • frontend/eslint.config.js (2 hunks)
  • frontend/index.html (1 hunks)
  • frontend/package.json (2 hunks)
  • frontend/postcss.config.cjs (0 hunks)
  • frontend/public/site.webmanifest (1 hunks)
  • frontend/src/App.tsx (1 hunks)
  • frontend/src/components/auth/login-form.tsx (1 hunks)
  • frontend/src/components/auth/login-forn.tsx (0 hunks)
  • frontend/src/components/auth/oauth-buttons.tsx (0 hunks)
  • frontend/src/components/auth/totp-form.tsx (1 hunks)
  • frontend/src/components/icons/generic.tsx (1 hunks)
  • frontend/src/components/icons/google.tsx (1 hunks)
  • frontend/src/components/language-selector/language-selector.tsx (0 hunks)
  • frontend/src/components/language/language.tsx (1 hunks)
  • frontend/src/components/layout/layout.tsx (1 hunks)
  • frontend/src/components/layouts/layout.tsx (0 hunks)
  • frontend/src/components/ui/button.tsx (1 hunks)
  • frontend/src/components/ui/card.tsx (1 hunks)
  • frontend/src/components/ui/form.tsx (1 hunks)
  • frontend/src/components/ui/input-otp.tsx (1 hunks)
  • frontend/src/components/ui/input.tsx (1 hunks)
  • frontend/src/components/ui/label.tsx (1 hunks)
  • frontend/src/components/ui/oauth-button.tsx (1 hunks)
  • frontend/src/components/ui/select.tsx (1 hunks)
  • frontend/src/components/ui/separator.tsx (1 hunks)
  • frontend/src/components/ui/sonner.tsx (1 hunks)
  • frontend/src/context/app-context.tsx (1 hunks)
  • frontend/src/context/user-context.tsx (1 hunks)
  • frontend/src/icons/google.tsx (0 hunks)
  • frontend/src/index.css (1 hunks)
  • frontend/src/lib/hooks/use-is-mounted.ts (0 hunks)
  • frontend/src/lib/i18n/i18n.ts (1 hunks)
  • frontend/src/lib/i18n/locales/en-US.json (3 hunks)
  • frontend/src/lib/i18n/locales/en.json (3 hunks)
  • frontend/src/lib/utils.ts (1 hunks)
  • frontend/src/main.tsx (1 hunks)
  • frontend/src/pages/continue-page.tsx (1 hunks)
  • frontend/src/pages/error-page.tsx (1 hunks)
  • frontend/src/pages/forgot-password-page.tsx (1 hunks)
  • frontend/src/pages/internal-server-error.tsx (0 hunks)
  • frontend/src/pages/login-page.tsx (1 hunks)
  • frontend/src/pages/logout-page.tsx (1 hunks)
  • frontend/src/pages/not-found-page.tsx (1 hunks)
  • frontend/src/pages/totp-page.tsx (1 hunks)
  • frontend/src/pages/unauthorized-page.tsx (1 hunks)
  • frontend/src/schemas/app-context-schema.ts (1 hunks)
  • frontend/src/schemas/login-schema.ts (1 hunks)
  • frontend/src/schemas/totp-schema.ts (1 hunks)
  • frontend/src/schemas/user-context-schema.ts (1 hunks)
  • frontend/src/utils/utils.ts (0 hunks)
  • frontend/tsconfig.app.json (1 hunks)
  • frontend/tsconfig.json (1 hunks)
  • frontend/vite.config.ts (2 hunks)
  • internal/handlers/handlers.go (4 hunks)
  • internal/types/api.go (1 hunks)
  • internal/types/config.go (2 hunks)
💤 Files with no reviewable changes (10)
  • frontend/src/lib/hooks/use-is-mounted.ts
  • frontend/src/components/layouts/layout.tsx
  • frontend/src/pages/internal-server-error.tsx
  • frontend/src/components/language-selector/language-selector.tsx
  • frontend/src/components/auth/oauth-buttons.tsx
  • frontend/src/icons/google.tsx
  • frontend/postcss.config.cjs
  • frontend/src/components/auth/login-forn.tsx
  • frontend/src/utils/utils.ts
  • .github/workflows/translations.yml
🧰 Additional context used
🧬 Code Graph Analysis (19)
frontend/src/components/language/language.tsx (2)
frontend/src/lib/i18n/locales.ts (2)
  • SupportedLanguage (34-34)
  • languages (1-32)
frontend/src/components/ui/select.tsx (5)
  • Select (173-173)
  • SelectTrigger (181-181)
  • SelectValue (182-182)
  • SelectContent (174-174)
  • SelectItem (176-176)
frontend/src/components/ui/input.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
internal/handlers/handlers.go (1)
internal/types/config.go (1)
  • Config (4-38)
frontend/src/pages/forgot-password-page.tsx (2)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/components/ui/card.tsx (4)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
frontend/src/pages/not-found-page.tsx (2)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/context/user-context.tsx (2)
internal/types/types.go (1)
  • UserContext (45-55)
frontend/src/schemas/user-context-schema.ts (1)
  • UserContextSchema (13-13)
frontend/src/components/ui/label.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/layout/layout.tsx (2)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/components/language/language.tsx (1)
  • LanguageSelector (12-35)
frontend/src/components/ui/separator.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/card.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/main.tsx (6)
frontend/src/App.tsx (1)
  • App (4-12)
frontend/src/pages/error-page.tsx (1)
  • ErrorPage (9-20)
frontend/src/context/app-context.tsx (1)
  • AppContextProvider (8-23)
frontend/src/context/user-context.tsx (1)
  • UserContextProvider (8-23)
frontend/src/components/layout/layout.tsx (1)
  • Layout (4-20)
frontend/src/components/ui/sonner.tsx (1)
  • Toaster (23-23)
frontend/src/components/auth/login-form.tsx (4)
frontend/src/schemas/login-schema.ts (2)
  • LoginSchema (8-8)
  • loginSchema (3-6)
frontend/src/components/ui/form.tsx (6)
  • Form (158-158)
  • FormField (164-164)
  • FormItem (159-159)
  • FormLabel (160-160)
  • FormControl (161-161)
  • FormMessage (163-163)
frontend/src/components/ui/input.tsx (1)
  • Input (21-21)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/context/app-context.tsx (2)
internal/types/api.go (1)
  • AppContext (45-56)
frontend/src/schemas/app-context-schema.ts (1)
  • AppContextSchema (14-14)
frontend/src/pages/logout-page.tsx (5)
frontend/src/context/user-context.tsx (1)
  • useUserContext (25-35)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/lib/utils.ts (1)
  • capitalize (17-19)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/components/ui/input-otp.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/pages/continue-page.tsx (5)
frontend/src/context/user-context.tsx (1)
  • useUserContext (25-35)
frontend/src/context/app-context.tsx (1)
  • useAppContext (25-33)
frontend/src/lib/utils.ts (1)
  • isValidUrl (8-15)
frontend/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardFooter (87-87)
frontend/src/components/ui/button.tsx (1)
  • Button (77-77)
frontend/src/components/ui/button.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/form.tsx (2)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
frontend/src/components/ui/label.tsx (1)
  • Label (22-22)
frontend/src/components/ui/select.tsx (1)
frontend/src/lib/utils.ts (1)
  • cn (4-6)
🪛 Biome (1.9.4)
frontend/tsconfig.app.json

[error] 3-3: Expected a property but instead found '// Resolve paths'.

Expected a property here.

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 7-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

🪛 LanguageTool
README.md

[uncategorized] ~17-~17: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... docker apps. It is designed for traefik but it can be extended to work with other r...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~25-~25: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...inyauth is intended for homelab use only and it is not made for production use cases...

(COMMA_COMPOUND_SENTENCE)


[typographical] ~25-~25: It seems that a comma is missing.
Context: ...e looking for something production ready please use authentik...

(IF_PLEASE_COMMA)


[style] ~37-~37: Consider removing “of” to be more concise
Context: ...ou can find documentation and guides on all of the available configuration of tinyauth in ...

(ALL_OF_THE)


[uncategorized] ~37-~37: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ...tion and guides on all of the available configuration of tinyauth in the [website](https://ti...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[uncategorized] ~37-~37: The preposition “on” seems more likely in this position than the preposition “in”.
Context: ...the available configuration of tinyauth in the website. #...

(AI_EN_LECTOR_REPLACEMENT_PREPOSITION_IN_ON)

🪛 markdownlint-cli2 (0.17.2)
README.md

23-23: Blank line inside blockquote
null

(MD028, no-blanks-blockquote)

🔇 Additional comments (48)
frontend/package.json (1)

2-2: Comprehensive UI framework migration looks good

The package has been renamed to "tinyauth-shadcn" and completely migrated from Mantine UI to a custom implementation using Radix UI primitives with Tailwind CSS styling. This aligns with the Shadcn UI approach.

Note that React v19.0.0 and Tailwind CSS v4.1.4 are very recent releases - ensure you've tested thoroughly as these versions might introduce stability issues in production environments.

Also applies to: 13-39, 42-56

internal/types/api.go (1)

54-54: Background image field addition looks good

The new BackgroundImage field in the AppContext struct allows for a configurable background image to be passed to the frontend. The field follows the existing pattern of the struct with proper JSON tagging.

Dockerfile (2)

12-12: Lock file format update is correct

Changed the Bun lock file reference from bun.lockb to bun.lock, which matches the actual file format being used in the project.


23-24: PostCSS config removal is appropriate

The line for copying postcss.config.cjs was removed, which is consistent with the migration from PostCSS (used with Mantine) to Tailwind CSS. This aligns with the overall UI framework changes.

frontend/src/schemas/app-context-schema.ts (1)

14-14: Type renaming improves consistency.

Changing from AppContextSchemaType to AppContextSchema makes the naming more consistent and removes redundancy (since "Type" was unnecessary).

frontend/src/components/icons/generic.tsx (1)

3-3: Component renamed to better reflect its purpose.

Renaming from OAuthIcon to GenericIcon is a good change that better describes the component's purpose within the new UI system.

frontend/Dockerfile.dev (1)

6-6: Fixed Bun lock file name.

The change from bun.lockb to bun.lock correctly references the actual filename used by Bun for its lock file.

frontend/src/schemas/login-schema.ts (1)

8-8: Type renaming improves consistency.

Changing from LoginFormValues to LoginSchema aligns with other schema type names in the project.

frontend/src/schemas/user-context-schema.ts (2)

4-11: Improved code formatting and field organization

The consistent indentation and field reordering improve readability. Adding a trailing comma after the last field is a good practice in TypeScript that prevents errors when adding new fields in the future.


13-13: Type naming consistency improved

Renaming the type from UserContextSchemaType to UserContextSchema provides better consistency with other schema types in the codebase and follows a cleaner naming convention.

frontend/tsconfig.json (1)

6-12: Effective path aliasing configuration added.

The addition of path aliasing in tsconfig.json enables cleaner imports using @/* throughout the codebase. This properly aligns with the similar configurations in tsconfig.app.json and vite.config.ts.

frontend/eslint.config.js (3)

6-6: Good addition of TanStack Query plugin.

Adding the TanStack Query ESLint plugin aligns with the frontend refactoring that introduces React Query hooks for authentication flows.


20-20: Plugin configuration correctly added.

The plugin is properly registered in the plugins section.


28-28: Appropriate rule configuration for query hooks.

Setting @tanstack/query/exhaustive-deps to "error" is a good practice that will help ensure query dependencies are properly declared, preventing stale queries and unnecessary refetches.

frontend/vite.config.ts (3)

1-4: Good migration to Tailwind CSS and updated React plugin.

The switch from @vitejs/plugin-react-swc to @vitejs/plugin-react and the addition of the Tailwind CSS plugin align with the broader UI framework migration described in the PR summary.


8-13: Consistent path alias configuration.

The alias configuration here correctly maps "@" to "./src", consistent with the TypeScript configuration in tsconfig.json. This enables cleaner imports throughout the codebase.


23-23: Security consideration with allowedHosts setting.

Setting allowedHosts: true allows requests from any host. While convenient for development, ensure this is appropriate for your deployment environment.

If this configuration is used in production, consider restricting to specific hosts instead.

frontend/src/schemas/totp-schema.ts (1)

7-7: Type export looks good.

Exporting the TypeScript type derived from the Zod schema is a good practice that ensures type consistency throughout the application.

frontend/src/App.tsx (2)

7-9: LGTM - Redirection flow matches intended application behavior

The clean, straightforward implementation correctly directs users based on their authentication status. Since this is an authentication service without a dashboard (as confirmed in previous discussions), redirecting logged-in users to "/logout" is the intended behavior.


11-11: LGTM - Clear fallback redirection

The default redirection to the login page for unauthenticated users is appropriate and clearly implemented.

frontend/src/components/icons/google.tsx (1)

3-30: Well-structured SVG component for Google icon

The implementation follows React best practices for SVG icons, accepting standard SVG props and using proper color values for the Google logo.

frontend/components.json (1)

1-21: Good shadcn/ui configuration

This configuration properly sets up shadcn/ui components with Tailwind CSS, using the "new-york" style preset and appropriate aliases for module imports. The configuration will help maintain consistent UI components throughout the application.

internal/handlers/handlers.go (2)

276-276: Good usage of temporary redirect for login page

The change to use http.StatusTemporaryRedirect for redirecting to the login page is appropriate, as it ensures the browser won't cache the redirect.


503-503: Properly added BackgroundImage field to the AppContext

The BackgroundImage field is correctly added to the AppContext struct in line with the configuration option propagation pattern used throughout the rest of the application.

frontend/src/lib/utils.ts (2)

4-6: Good utility for combining Tailwind CSS classes

The cn function correctly uses clsx and tailwind-merge to handle class name combinations and conflicts, following best practices for Tailwind CSS.


8-15: Robust URL validation function

The URL validation approach using the native URL constructor is a clean and effective way to validate URLs.

frontend/src/pages/forgot-password-page.tsx (1)

16-23: LGTM - Clean card-based UI implementation.

The refactoring from Mantine UI to the new card-based UI components is clean and well-structured. The component hierarchy and styling look appropriate.

frontend/src/lib/i18n/i18n.ts (1)

9-11:

Details

✅ Verification successful

Verify impact of removing HTTP backend.

The simplification of the i18n backend configuration by removing chained backends and HTTP fallback is good for maintenance, but ensure the application doesn't rely on the removed functionality for loading translations in production.


🏁 Script executed:

#!/bin/bash
# Check for any references to HTTP backend or chained backend in the codebase

echo "Checking for references to HTTP backend:"
rg -i "httpbackend|http-backend" --type ts --type tsx

echo "Checking for references to chained backend:"
rg -i "chainedbackend|chained-backend" --type ts --type tsx

echo "Checking for other backend implementations:"
rg -i "use\(.*backend\)" --type ts --type tsx

Length of output: 534


🏁 Script executed:

#!/bin/bash
# Search for any i18next HTTP backend import usage
rg -n "i18next-http-backend" -g '*.ts' -g '*.tsx'

# Search for any other backend implementations or chaining in use()
rg -n "use\(" -g '*.ts' -g '*.tsx' | grep -i backend || echo "No direct backend chaining found."

# Search for any import statements that include "backend"
rg -n "import.*backend" -g '*.ts' -g '*.tsx' || echo "No backend imports detected."

# Broad search for the term "backend" in code
rg -n "backend" -g '*.ts' -g '*.tsx' || echo "No occurrences of 'backend' found."

Length of output: 516


Confirm Safe Removal of HTTP Backend

We’ve verified there are no remaining references to i18next-http-backend or chained backends—translation loading is now exclusively handled by i18next-resources-to-backend via dynamic JSON imports. Ensure your locale JSON files are included in the production bundle (Vite supports dynamic JSON imports by default).

  • No i18next-http-backend imports or chained .use() calls found.
  • Only .use(resourcesToBackend(...)) is used to load ./locales/${language}.json.
  • Vite config does not reference any translation endpoints—verify locale files exist under frontend/src/lib/i18n/locales so they’re bundled.
frontend/src/components/layout/layout.tsx (1)

8-18: LGTM - Good implementation addressing previous concerns.

The component correctly addresses the previous review comments by:

  1. Using relative positioning on the container, which provides a positioned ancestor for the absolutely positioned LanguageSelector
  2. Using inline styles for background image instead of string interpolation in className, which is safer
frontend/src/context/app-context.tsx (1)

28-30: LGTM: Improved null check

Simplified null check to use JavaScript's truthy/falsy behavior, which is more idiomatic.

frontend/src/context/user-context.tsx (1)

28-32: LGTM: Improved error handling

The updated conditional check and error message formatting improve code readability.

frontend/src/pages/not-found-page.tsx (2)

1-11: LGTM: Clean imports and React Router usage

Good job switching to component-specific imports and using React Router's navigation hook instead of direct window location changes.


17-26: LGTM: Improved UI components and structure

The refactored UI uses more semantic component structure with dedicated Card components and proper styling through Tailwind classes. The flex container in CardFooter provides better layout control.

frontend/src/components/ui/separator.tsx (1)

8-26: Looks good: Clean Radix UI integration with Tailwind CSS

The Separator component is well-implemented, with proper prop forwarding and accessibility support via the decorative prop. The Tailwind styling uses data attributes correctly to handle both horizontal and vertical orientations.

frontend/src/components/auth/login-form.tsx (2)

25-27: LGTM: Clean form setup with Zod validation

The form is properly configured with the Zod resolver for validation.


75-77: Great use of the loading prop

Good implementation of the loading state on the submit button, ensuring a better user experience by preventing multiple submissions.

frontend/src/pages/unauthorized-page.tsx (1)

38-60: Clean UI structure with Card components

The refactoring to use the Card component suite creates a more consistent UI structure across the application. The layout looks clean and appropriately uses flex-col for stacking the button in the footer.

cmd/root.go (3)

94-94: Properly integrated background image in handlers config

The new BackgroundImage field is correctly added to the HandlersConfig struct.


209-209: Good default for background image flag

The default value of /background.jpg is reasonable, assuming this path is relative to the application's static assets directory.


243-243:

Details

✅ Verification successful

Verify background image path security

When accepting user-configurable paths for files that will be served to the browser, ensure proper validation to prevent directory traversal attacks.


🏁 Script executed:

#!/bin/bash
# Check if there's any validation for the background image path

# Look for any path validation or sanitization for BackgroundImage
rg -A 5 -B 5 "BackgroundImage.*path|path.*BackgroundImage" --type go

Length of output: 70


Let's inspect how the background-image value is used and whether any path sanitization is applied in cmd/root.go:


🏁 Script executed:

#!/bin/bash
# Show the BindEnv and its surrounding context
sed -n '230,300p' cmd/root.go

# Find where the bound value is retrieved
rg -n 'GetString.*background-image' cmd/root.go

# Look for any filepath-based sanitization in this file
rg -n 'filepath\.' cmd/root.go

Length of output: 920


To determine whether the background-image path is ever retrieved and sanitized, let’s search the entire repo for its usage and any path‐cleaning calls:


🏁 Script executed:

#!/bin/bash
# 1. Find every occurrence of "background-image" (in code, templates, assets)
rg -n "background-image" -A3 -B3 .

# 2. Search for retrieval calls (viper, flags, etc.)
rg -n "Get.*background-image" -A3 -B3 .

# 3. Look for common path‐sanitization functions
rg -n "filepath\.Clean" -A3 -B3 .
rg -n "path\.Clean" -A3 -B3 .
rg -n "filepath\.Abs" -A3 -B3 .

Length of output: 1738


To pinpoint where and how the background-image value is actually loaded and used (and whether it’s ever sanitized), let’s search for the config unmarshal step and all references to BackgroundImage, plus any file‐serving calls:


🏁 Script executed:

#!/bin/bash
# 1. Find where we unmarshal viper into the config struct
rg -n "viper\\.Unmarshal" -A3 -B3 .

# 2. Find every use of the BackgroundImage field
rg -n "BackgroundImage" -A5 -B5 .

# 3. Look for any HTTP file‐serving calls that might use that path
rg -n "ServeFile" -A3 -B3 .
rg -n "FileServer" -A3 -B3 .

Length of output: 4321


Let’s confirm that the server is using an embedded filesystem (so user paths can’t escape the dist bundle):


🏁 Script executed:

#!/bin/bash
# Show the top of the internal API file to see embed declarations
sed -n '1,60p' internal/api/api.go

# Look explicitly for any go:embed directives
rg -n "//go:embed" internal/api/api.go

Length of output: 1352


No directory traversal risk for background-image path

The background-image value is only returned in the API JSON and never directly used for file lookups. Static assets are served exclusively from an embedded filesystem (http.FileServer(http.FS(dist))), which rejects any .. segments and confines requests to the bundled dist directory. No additional sanitization is needed.

frontend/src/index.css (1)

1-4: Verify plugin names & directives.

  • @import "tw-animate-css"; → the popular package is tw-anim**ate**, not tw-animate-css.
  • @custom-variant and @theme are not Tailwind core directives. They rely on additional PostCSS plugins (tailwindcss-variants, tailwindcss-themer, etc.). Make sure these plugins are installed & configured; otherwise Tailwind will throw on build.
frontend/src/components/ui/card.tsx (1)

78-78: Consider consistent padding regardless of border presence

The conditional padding based on the presence of a border class ([.border-t]:pt-6) could lead to inconsistent layouts. Consider whether padding should be applied consistently.

Are there cases in the application where CardFooter is used without a top border? If it's always used with a border or if the padding should always be applied, consider removing the conditional selector:

-className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
+className={cn("flex items-center px-6 pt-6", className)}

If the conditional styling is intentional, consider adding a comment explaining when/why it's used.

frontend/src/pages/login-page.tsx (1)

97-107: Add missing dependencies to useEffect dependency array

The empty dependency array ([]) causes the effect to run only on mount, which is better than running on every render. However, you should include all values from the enclosing scope that the effect depends on:

-  }, []);
+  }, [
+    isMounted,
+    oauthConfigured,
+    configuredProviders,
+    oauthAutoRedirect,
+    redirectUri,
+    oauthMutation,
+  ]);
frontend/src/components/ui/select.tsx (1)

7-11: LGTM! Well-structured Select component implementation

Good job implementing this component with proper TypeScript props handling and Radix UI integration.

frontend/index.html (2)

10-10: Apple web app title added
The <meta name="apple-mobile-web-app-title" content="Tinyauth" /> tag is correctly included for iOS web app bookmarks. Ensure this matches your branding guidelines.


11-11: Verify updated manifest path
The manifest link was changed to /site.webmanifest. Confirm that site.webmanifest exists at the root of your public directory and that its icons paths align with your new favicon assets.

frontend/tsconfig.app.json (2)

3-3: Comment supported in TSConfig JSONC
Although static analyzers flagged the // Resolve paths line, TypeScript config files are parsed as JSONC and support comments. Ensure your linter/parser is configured to handle JSONC so these comments don’t cause build errors.

🧰 Tools
🪛 Biome (1.9.4)

[error] 3-3: Expected a property but instead found '// Resolve paths'.

Expected a property here.

(parse)


4-7: Cleaner imports via path alias
Adding "baseUrl": "." and the "@/*": ["./src/*"] mapping streamlines imports across the frontend and aligns with your Vite configuration. This will improve maintainability and reduce relative-path complexity.

🧰 Tools
🪛 Biome (1.9.4)

[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 4-4: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-5: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 5-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)


[error] 7-7: End of file expected

Use an array for a sequence of values: [1, 2]

(parse)

Comment thread frontend/.prettierignore Outdated
Comment thread frontend/src/schemas/login-schema.ts Outdated
Comment thread frontend/src/schemas/totp-schema.ts
Comment thread internal/types/config.go Outdated
Comment thread frontend/src/context/user-context.tsx
Comment thread frontend/src/pages/logout-page.tsx
Comment thread frontend/src/pages/logout-page.tsx
Comment thread frontend/src/main.tsx
Comment thread frontend/src/lib/i18n/locales/en.json Outdated
Comment thread frontend/src/pages/login-page.tsx
@steveiliop56 steveiliop56 merged commit 318f009 into main May 20, 2025
4 of 5 checks passed
@steveiliop56 steveiliop56 added this to the v3.4.0 milestone May 20, 2025
@Rycochet Rycochet deleted the feat/new-ui branch April 1, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants