DEV Community

Ramu Narasinga
Ramu Narasinga

Posted on

Security best practices in Umami codebase - part 1.1

Inspired by BulletProof React, I applied its codebase architecture concepts to the Umami codebase.

This article focuses only on the security best practices used in Umami codebase.

Prerequisite

  1. Security best practices in Umami codebase — part 1.0

Approach

Like I mentioned in the part 1.0, in this article, we will learn how the login works in the Umami codebase.

  1. Locate the login form

  2. handleSubmit function

Locate the login form

Since Umami uses Next.js app router, it is straight forward to locate the login form.

You will find the following code at umami/src/app/login/page.tsx. Yeah, it is that straight forward.

import type { Metadata } from 'next';
import { LoginPage } from './LoginPage';

export default async function () {
  if (process.env.DISABLE_LOGIN || process.env.CLOUD_MODE) {
    return null;
  }

  return <LoginPage />;
}

export const metadata: Metadata = {
  title: 'Login',
};
Enter fullscreen mode Exit fullscreen mode

The LoginPage component is colocated in the same login folder. Umami is consistent with colocating the components within a route. Consistency “reduces” the tech debt.

'use client';
import { Column } from '@umami/react-zen';
import { LoginForm } from './LoginForm';

export function LoginPage() {
  return (
    <Column alignItems="center" height="100vh" backgroundColor="2" paddingTop="12">
      <LoginForm />
    </Column>
  );
}
Enter fullscreen mode Exit fullscreen mode

LoginForm is defined as shown below:

import {
  Column,
  Form,
  FormButtons,
  FormField,
  FormSubmitButton,
  Heading,
  Icon,
  PasswordField,
  TextField,
} from '@umami/react-zen';
import { useRouter } from 'next/navigation';
import { useMessages, useUpdateQuery } from '@/components/hooks';
import { Logo } from '@/components/svg';
import { setClientAuthToken } from '@/lib/client';
import { setUser } from '@/store/app';

export function LoginForm() {
  const { formatMessage, labels, getErrorMessage } = useMessages();
  const router = useRouter();
  const { mutateAsync, error } = useUpdateQuery('/auth/login');

  const handleSubmit = async (data: any) => {
    await mutateAsync(data, {
      onSuccess: async ({ token, user }) => {
        setClientAuthToken(token);
        setUser(user);
        router.push('/');
      },
    });
  };

  return (
    <Column justifyContent="center" alignItems="center" gap="6">
      <Icon size="lg">
        <Logo />
      </Icon>
      <Heading>umami</Heading>
      <Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
        <FormField
          label={formatMessage(labels.username)}
          data-test="input-username"
          name="username"
          rules={{ required: formatMessage(labels.required) }}
        >
          <TextField autoComplete="username" />
        </FormField>

        <FormField
          label={formatMessage(labels.password)}
          data-test="input-password"
          name="password"
          rules={{ required: formatMessage(labels.required) }}
        >
          <PasswordField autoComplete="current-password" />
        </FormField>
        <FormButtons>
          <FormSubmitButton
            data-test="button-submit"
            variant="primary"
            style={{ flex: 1 }}
            isDisabled={false}
          >
            {formatMessage(labels.login)}
          </FormSubmitButton>
        </FormButtons>
      </Form>
    </Column>
  );
}
Enter fullscreen mode Exit fullscreen mode

To understand what is going on, it comes down to studying:

  • useUpdateQuery

  • handleSubmit

handleSubmit function

When you submit the login form, it is handled as defined below:

const { mutateAsync, error } = useUpdateQuery('/auth/login');

const handleSubmit = async (data: any) => {
  await mutateAsync(data, {
    onSuccess: async ({ token, user }) => {
      setClientAuthToken(token);
      setUser(user);
      router.push('/');
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

useUpdateQuery

Umami uses Tanstack React Query. This useUpdateQuery is a reusable update hook.

import { useToast } from '@umami/react-zen';
import type { ApiError } from '@/lib/types';
import { useApi } from '../useApi';
import { useModified } from '../useModified';

export function useUpdateQuery(path: string, params?: Record<string, any>) {
  const { post, useMutation } = useApi();
  const query = useMutation<any, ApiError, Record<string, any>>({
    mutationFn: (data: Record<string, any>) => post(path, { ...data, ...params }),
  });
  const { touch } = useModified();
  const { toast } = useToast();

  return { ...query, touch, toast };
}
Enter fullscreen mode Exit fullscreen mode

onSuccess

onSuccess is a callback defined that gets executed when the user logs in successfully.

onSuccess: async ({ token, user }) => {
  setClientAuthToken(token);
  setUser(user);
  router.push('/');
},
Enter fullscreen mode Exit fullscreen mode

Now this does three things:

  • setClientAuthToken

setClientAuthToken updates the local storage with the auth token. 

  • setUser
export function setUser(user: object) {
  store.setState({ user });
}
Enter fullscreen mode Exit fullscreen mode

setUser updates the Zustand store with the currently logged in user information.

and finally you are redirected to the / home route.

About me:

Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.

Email: ramu.narasinga@gmail.com

I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com

References:

  1. umami/blob/master/src/app/login/page.tsx

  2. umami/src/app/login/LoginPage.tsx

  3. umami/src/app/login/LoginForm.tsx

  4. umami/blob/master/src/lib/client.ts#L8

  5. umami/blob/master/src/lib/storage.ts#L1

  6. umami/…/hooks/queries/useUpdateQuery.ts#L6

Top comments (0)