Feat/new UI#153
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughThis 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
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
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit 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. Note ⚡️ Faster reviews with cachingCodeRabbit 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 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (24)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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 setupThe 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:
- If you're using a custom Tailwind config file, specify its path in the "config" field (currently empty)
- 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 readabilityWhile 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 textThe 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 beSeparatorWithChildren(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 explicitdefaultValuesto avoid uncontrolled-to-controlled warnings
react-hook-formwill complain (in strict mode) if any field value changes fromundefined→string.
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 eventsBecause 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 usepointer-events-noneon 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
loadingis forwarded to theButton, but unless the component itself internally setsdisabled, 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 redirectUsers who reach
/unauthorizedoften 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:Toasterplaced insideLayoutmay render beneath modals or portalsIf
Layoutappliesoverflow-hiddenor transforms, the toast portal can be clipped.
Consider mountingToasterdirectly underQueryClientProvider(sibling toLayout) so it sits at the document root.No code change required if your
Layoutdoesn’t clip overflow; just double-check.frontend/package.json (2)
30-33: React 19 & React-DOM 19 are pre-release; expect ecosystem churnLocking to
"^19.0.0"will pull the first stable 19.x once released, but today resolves to the latest prerelease.
Third-party libraries (includingreact-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/vitehas been deprecated in favour of the officialtailwindcsspluginThe recommended approach with Vite ≥4:
npm i -D tailwindcss postcss autoprefixer npx tailwindcss init -pand add
tailwindcsstopostcss.config.js, not via a Vite plugin.Confirm that
@tailwindcss/vitestill meets your needs or migrate to the standard setup to avoid maintenance issues.frontend/src/pages/logout-page.tsx (1)
35-37: Remove unnecessaryasynckeyword insidesetTimeoutcallbackThe callback does not contain any
awaitstatements (andsetTimeoutignores returned promises).
Droppingasyncavoids 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 errorsString-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: ProvidedefaultValuesto avoid uncontrolled-to-controlled warningReact-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
setTimeoutinsideonSuccesswon’t be cleared if the component unmounts within 500 ms (e.g. route change triggered elsewhere), leaving a dangling callback.- Consider using
replace: trueto 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: Exposerefby usingReact.forwardRefMost UI primitives forward refs so callers can focus a card or measure its size.
Wrapping the root component withforwardRefcosts 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 titlesScreen-reader users benefit from proper heading structure. Swapping the
divfor anh2(or the appropriate level) insideCardTitlepreserves 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 variableThe 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 InputOTPSeparatorUnlike 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 approachThe custom variant
@custom-variant dark (&:is(.dark *))might be less maintainable than Tailwind's built-in dark mode. Tailwind's approach would be to usedark:prefix in class names with thedarkMode: '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 stylingWhile 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-1that can be applied only where needed.frontend/src/components/ui/button.tsx (2)
8-10: Consider extracting long base className into a variableSimilar 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 renderingThe 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 areaThe 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
SelectTriggercomponent 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
⛔ Files ignored due to path filters (18)
assets/logo-rounded.pngis excluded by!**/*.pngassets/logo-solid.jpgis excluded by!**/*.jpgassets/logo.pngis excluded by!**/*.pngassets/screenshot.pngis excluded by!**/*.pngfrontend/bun.lockis excluded by!**/*.lockfrontend/bun.lockbis excluded by!**/bun.lockbfrontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/public/android-chrome-192x192.pngis excluded by!**/*.pngfrontend/public/android-chrome-512x512.pngis excluded by!**/*.pngfrontend/public/apple-touch-icon.pngis excluded by!**/*.pngfrontend/public/background.jpgis excluded by!**/*.jpgfrontend/public/favicon-16x16.pngis excluded by!**/*.pngfrontend/public/favicon-32x32.pngis excluded by!**/*.pngfrontend/public/favicon-96x96.pngis excluded by!**/*.pngfrontend/public/favicon.icois excluded by!**/*.icofrontend/public/favicon.svgis excluded by!**/*.svgfrontend/public/web-app-manifest-192x192.pngis excluded by!**/*.pngfrontend/public/web-app-manifest-512x512.pngis 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 goodThe 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 fieldThe addition of the
BackgroundImagefield in theAppContextstruct 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 appropriateThe 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 goodThe updated schema format is cleaner and more consistent. The addition of
oauthAutoRedirectandbackgroundImagefields properly supports the new features introduced in this PR.
14-14: Type naming convention improvementRenaming from
AppContextSchemaTypetoAppContextSchemaremoves redundancy and aligns with modern TypeScript naming conventions.frontend/Dockerfile.dev (1)
6-6: Correct lockfile filename used: The Dockerfile now correctly copiesbun.lockinstead of the deprecatedbun.lockb, ensuring deterministic installs and proper cache utilization.frontend/src/schemas/login-schema.ts (2)
4-5: Consistent indentation improved: Theusernameandpasswordfields 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 theloginSchemaobject. 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
LoginFormValueshave been removed
Ranrg -l 'LoginFormValues' -n .and confirmed no matches in the codebase. TheLoginSchemaalias 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.
• WithoutdarkMode: '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.webmanifestand 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
Thesite.webmanifestis present atfrontend/public/site.webmanifest, so the<link rel="manifest" href="/site.webmanifest" />reference infrontend/index.htmlis 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: AddedcompilerOptions.baseUrlandpathsto support@/*imports and maintain consistency withtsconfig.app.jsonandvite.config.ts.frontend/eslint.config.js (1)
6-6: Good addition of TanStack Query lintingAdding 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 variablesThe new environment variables for forgot password message, OAuth auto-redirect, and background image are clearly documented with appropriate example values. The
nonedefault forOAUTH_AUTO_REDIRECTis 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 fieldsThe code formatting is improved, and the commented placeholder fields for
nameand
13-13: Type naming consistency improvementRenaming from
UserContextSchemaTypetoUserContextSchemafollows 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 frameworkSwitching from
@vitejs/plugin-react-swcto@vitejs/plugin-reactand adding the Tailwind plugin aligns with the UI framework migration mentioned in the summary.
8-13: Good addition of path aliasingAdding 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 settingSetting
allowedHosts: trueallows 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 ConfigThe 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 configurationThe 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 redirectionRedirecting 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 componentThe 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
cnfunction elegantly combines Tailwind CSS classes usingclsxandtailwind-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
resourcesToBackendwith 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
cnutility 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 supportThe 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 pageThe 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.goLength of output: 571
Background image configuration is validated
TheBackgroundImagefield ininternal/types/config.gocarries avalidate:"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 typingThe 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 contextUpdating the context type to use
AppContextSchemafrom a centralized schema file is a good practice for type consistency across the application.
13-16: Simplified query implementationThe 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 checkUsing
!contextinstead 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
UserContextSchemaTypetoUserContextSchemato match the schema definition.
6-6: Type definition updated correctly.The context type has been updated to use the renamed
UserContextSchematype, maintaining type safety.
13-15: Query simplification and improved data handling.The query has been improved by:
- Simplifying the query key from
["userContext"]to["user"]- Using a cleaner promise chain in the query function
- Destructuring with better variable names (
isFetchinginstead ofisLoading)This makes the code more readable and maintainable.
18-18: Error handling condition updated correctly.The error handling condition now uses
isFetchinginstead ofisLoading, maintaining consistency with the variable names above.
22-22: Provider value updated to match variable naming.The context provider now correctly passes
datarather thanuserContext, 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
useNavigatefrom React Router to implement client-side navigation.
14-15: React Router hook initialization.Added the
useNavigatehook 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:
- Using
Cardas the main container with responsive min-width- Using semantic components like
CardHeader,CardTitle, etc.- Setting up proper flexbox layout for the button container
- 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'snavigate("/"), 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
Separatorcomponent is correctly implemented:
- It wraps Radix UI's separator primitive
- Provides sensible defaults for orientation and decorative props
- Uses the
cnutility function to merge classes- 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
BackgroundImagefield has been correctly added to theHandlersConfigstruct 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-imagewith 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_IMAGEis 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: Verifyreact-router@7availability –createBrowserRouteris not exported by v6Imports come from
"react-router". In every stable release up to v6.23,createBrowserRouter&RouterProviderlive inreact-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 definitionThe
react-querymutation is clearly expressed and keyed; great use oftoastfor UX feedback.frontend/src/pages/totp-page.tsx (1)
16-17: Double-check import source foruseNavigate
useNavigateis re-exported fromreact-router, but the DOM implementation lives inreact-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 fromreact-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 componentThe component is well-designed with good prop typing and appropriate use of the
cnutility for class name composition. The data attributes enhance accessibility.frontend/src/index.css (3)
44-111: Well-organized color system with OKLCHThe 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 layersUsing the
@layer basedirective 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 organizationThe 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-authorityThe button variants are well-structured using
cvawith 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 stateThe 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 titleAdding a simplified login title variation is helpful for contexts where you don't want to specifically mention login methods.
22-22: Good HTML tag normalizationChanging from
<Code>to lowercase<code>is more consistent with HTML standards, which typically use lowercase tag names.
50-52: Good addition of error messagesThe 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:
- Proper context usage for form field state management
- Strong TypeScript typing with generics for form values
- Accessible components with proper ARIA attributes
- 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
useFormFieldhook 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
FormControlcomponent sets proper ARIA attributes for accessibility, includingaria-describedbythat dynamically changes based on error state, andaria-invalidfor 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:
- Uses data attributes for styling states
- Provides proper component composition
- Has strong TypeScript typing
- Includes proper accessibility attributes
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
frontend/src/pages/unauthorized-page.tsx (2)
13-18: Improved: Now using React Router hooks for location accessThe component now properly uses
useLocation()instead of directly accessingwindow.location, addressing the previous review comments. This approach is reactive and compatible with SSR.
27-27: Fixed: Corrected the misspelled i18n keyThe 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 logicThe early return with
<Navigate>inside the component body makes the flow harder to follow. Consider moving this check before setting up other hooks likeuseNavigate().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
📒 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 frameworkThe 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 frameworkThe 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:
- 1: https://react.dev/versions
- 2: https://react.dev/blog/2024/12/05/react-19
- 3: https://github.com/facebook/react/releases
- 4: https://endoflife.date/react
- 5: https://reactnative.dev/blog/2025/02/19/react-native-0.78
- 6: https://blog.codedthemes.com/whats-new-in-react-19/
- 7: https://www.npmjs.com/package/react?activeTab=versions
- 8: https://www.lucentinnovation.com/blogs/it-insights/react-native-0-78-react-19-version-update-guide
- 9: https://www.scalablepath.com/react/react-19
- 10: https://en.wikipedia.org/wiki/React_(software)
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 architectureThe 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.
There was a problem hiding this comment.
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
Thealt="Screenshot"is generic. Consider describing the content, e.g., “Tinyauth login screen.”-  + 
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
📒 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 awidthattribute for sizing.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 10
🔭 Outside diff range comments (1)
frontend/Dockerfile.dev (1)
1-22: 🛠️ Refactor suggestionMissing 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 accessUsing
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 issueConsider 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
absolutepositioning on theSelectTriggerrequires a positioned ancestor.frontend/src/context/app-context.tsx (1)
13-16:⚠️ Potential issueAdd 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 accessThe switch to
useLocation()anduseNavigate()is a significant improvement over directwindow.locationaccess, addressing the issues raised in previous reviews.
27-35: Fixed translation key typoThe previously identified typo in the translation key has been fixed (
unauthorizedLoginSubtitleinstead ofunaothorizedLoginSubtitle).frontend/src/pages/totp-page.tsx (1)
26-26: Encoderedirect_uriimmediately after extractionThe
redirectUriis 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 viewportsThe 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_urito prevent open redirect vulnerabilitiesThe
redirect_uriis 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 tsLength of output: 391
Ensure Safe Handling of
redirect_urito Prevent Open RedirectsPlease validate and sanitize the
redirect_uribefore using it. We already have anisValidUrlhelper infrontend/src/lib/utils.ts, so you can leverage that alongside a simple same-origin or relative-path check. For example, infrontend/src/pages/login-page.tsxaround 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
isValidUrlfrom@/lib/utilsandDOMPurifyif not already present.
• Sanitize the raw query-param value.
• UseisValidUrlplus an additional check (e.g.,startsWith("/")or comparingnew URL().origintowindow.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 concernThis is another instance of the same pattern using
window.location.hrefwith a sanitized but not fully validated URL.Apply the same solution as suggested above - replace with a call to a centralized
safeRedirectutility function.
122-125: Duplicate URL redirect security concernThis is the third instance of the same pattern using
window.location.hrefwith a sanitized but not fully validated URL.Apply the same solution as suggested above - replace with a call to a centralized
safeRedirectutility function.
65-68: 🛠️ Refactor suggestionCreate a centralized safe redirect function
The pattern of sanitizing URLs and directly assigning to
window.location.hrefis 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 issueSecurity 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 valueSimilar to the previous comment about FormFieldContext, the FormItemContext is created with an empty object as the default value.
Apply the same solution - use
undefinedas the default value and properly check before accessing properties.frontend/src/components/ui/select.tsx (2)
99-121: Add keyboard navigation tests for SelectItem componentThe 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 readabilitySimilar 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
backgroundImagefield 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 componentfrontend/src/pages/error-page.tsx (1)
9-20: Enhance error page with additional functionalityThe error page implementation is clean and follows good practices with proper localization. Consider enhancing it with:
- A button to return to the login page or retry the operation
- Display of specific error details when available
- 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 propertiesThe 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_urlandscopefor better PWA behavior- Non-maskable icons (purpose: "any") for browsers that don't support maskable icons
orientationpreferencefrontend/src/lib/utils.ts (1)
17-19: Consider handling edge cases in capitalize functionThe 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 SupportedLanguagewithout 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 suggestionThe imports are well-organized but for consistency with other UI components, consider using the
cnutility instead of directly usingtwMerge.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 structureThe 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 stylingThe 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:
- Replaces
twMergewithcnfor consistency with other UI components- Adds spacing between the icon and text
- Wraps elements in spans for better layout control
- Adds JSDoc documentation
README.md (4)
64-64: Fix typo in acknowledgementThere'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 phrasingConsider 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 nameThere'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 positioningUsing absolute positioning with a fixed
bottom-10value may cause layout issues when:
- The form has validation errors that change the field's height
- The viewport size changes
- 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-smare 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 customloadingprop (optional).If the
Buttoncomponent doesn’t natively handle aloadingprop, you can rely on the HTMLdisabledattribute 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 fromroutes.tsxand import it here:// routes.tsx export const routes: RouteObject[] = [ … ] // main.tsx const router = createBrowserRouter(routes);
70-82:Toasterplacement can live outsideLayout(nit).Placing
ToasterunderStrictModebut outside layout prevents it from inheritingoverflow:hiddenor 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: Globaloutline-ring/50utility 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 notificationsToast 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 experienceThe 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 selectorThe complex CSS selector with
@containerand 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 classThe
shadow-xsclass 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-xsfrom each individual variant.
55-66: Prevent layout shifts in loading stateWhen 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 messageThere'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 validationWhile 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 valueCreating 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 theuseFormFieldhook, 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
useFormFieldhook 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 propertiesThe error check should be performed before trying to access properties from
fieldContextto 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 FormMessageThe 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 readabilityThe 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 newOAUTH_AUTO_REDIRECTandBACKGROUND_IMAGEentries 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
Hardcodingclass="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
⛔ Files ignored due to path filters (18)
assets/logo-rounded.pngis excluded by!**/*.pngassets/logo-solid.jpgis excluded by!**/*.jpgassets/logo.pngis excluded by!**/*.pngassets/screenshot.pngis excluded by!**/*.pngfrontend/bun.lockis excluded by!**/*.lockfrontend/bun.lockbis excluded by!**/bun.lockbfrontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/public/android-chrome-192x192.pngis excluded by!**/*.pngfrontend/public/android-chrome-512x512.pngis excluded by!**/*.pngfrontend/public/apple-touch-icon.pngis excluded by!**/*.pngfrontend/public/background.jpgis excluded by!**/*.jpgfrontend/public/favicon-16x16.pngis excluded by!**/*.pngfrontend/public/favicon-32x32.pngis excluded by!**/*.pngfrontend/public/favicon-96x96.pngis excluded by!**/*.pngfrontend/public/favicon.icois excluded by!**/*.icofrontend/public/favicon.svgis excluded by!**/*.svgfrontend/public/web-app-manifest-192x192.pngis excluded by!**/*.pngfrontend/public/web-app-manifest-512x512.pngis 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 goodThe 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 goodThe new
BackgroundImagefield in theAppContextstruct 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 correctChanged the Bun lock file reference from
bun.lockbtobun.lock, which matches the actual file format being used in the project.
23-24: PostCSS config removal is appropriateThe line for copying
postcss.config.cjswas 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
AppContextSchemaTypetoAppContextSchemamakes 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
OAuthIcontoGenericIconis 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.lockbtobun.lockcorrectly 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
LoginFormValuestoLoginSchemaaligns with other schema type names in the project.frontend/src/schemas/user-context-schema.ts (2)
4-11: Improved code formatting and field organizationThe 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 improvedRenaming the type from
UserContextSchemaTypetoUserContextSchemaprovides 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-depsto "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-swcto@vitejs/plugin-reactand 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: trueallows 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 behaviorThe 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 redirectionThe 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 iconThe 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 configurationThis 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 pageThe change to use
http.StatusTemporaryRedirectfor 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 AppContextThe 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 classesThe
cnfunction correctly usesclsxandtailwind-mergeto handle class name combinations and conflicts, following best practices for Tailwind CSS.
8-15: Robust URL validation functionThe 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 tsxLength 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-backendor chained backends—translation loading is now exclusively handled byi18next-resources-to-backendvia dynamic JSON imports. Ensure your locale JSON files are included in the production bundle (Vite supports dynamic JSON imports by default).
- No
i18next-http-backendimports 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/localesso 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:
- Using relative positioning on the container, which provides a positioned ancestor for the absolutely positioned LanguageSelector
- 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 checkSimplified 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 handlingThe 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 usageGood 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 structureThe 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 CSSThe Separator component is well-implemented, with proper prop forwarding and accessibility support via the
decorativeprop. 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 validationThe form is properly configured with the Zod resolver for validation.
75-77: Great use of the loading propGood 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 componentsThe 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 configThe new BackgroundImage field is correctly added to the HandlersConfig struct.
209-209: Good default for background image flagThe default value of
/background.jpgis 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 goLength of output: 70
Let's inspect how the
background-imagevalue is used and whether any path sanitization is applied incmd/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.goLength of output: 920
To determine whether the
background-imagepath 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
distbundle):
🏁 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.goLength of output: 1352
No directory traversal risk for
background-imagepathThe
background-imagevalue 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 bundleddistdirectory. No additional sanitization is needed.frontend/src/index.css (1)
1-4: Verify plugin names & directives.
@import "tw-animate-css";→ the popular package istw-anim**ate**, nottw-animate-css.@custom-variantand@themeare 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 presenceThe 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 arrayThe 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 implementationGood 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 thatsite.webmanifestexists at the root of your public directory and that itsiconspaths align with your new favicon assets.frontend/tsconfig.app.json (2)
3-3: Comment supported in TSConfig JSONC
Although static analyzers flagged the// Resolve pathsline, 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)
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores
Style