wiki:Security

Security

const authjsConfig = {
  basePath: "/api/auth",
  trustHost: true,
  secret: process.env.AUTH_SECRET,
  adapter: AuthDrizzleAdapter(),
  session: {
    strategy: "jwt",
  },
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        username: { label: "Username", type: "text" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const username = typeof credentials?.username === "string" ? credentials.username : null;
        const password = typeof credentials?.password === "string" ? credentials.password : null;

        if(!username || !password || typeof username !== 'string' || typeof password !== 'string') { return null; }

        const user = await db.query.usersTable.findFirst({
            where: or(
                eq(usersTable.username, username),
                eq(usersTable.email, username)
            ),
          });

        if(!user) return null;

        const isPasswordValid = await bcrypt.compare(password, user.passwordHash);

        if(!isPasswordValid) return null;

          const admin = await db.query.adminsTable.findFirst({
              where: eq(adminsTable.userId, user.id),
          });

        return {
            id: String(user.id),
            username: user.username,
            email: user.email,
            isAdmin: !!admin
        }
      },
    }),
  ],

  callbacks: {
      async jwt({ token, user }) {
          if (user) {
              token.sub = user.id;

              const customUser = user as any;
              token.username = customUser.username;
              token.email = customUser.email;
              token.isAdmin = customUser.isAdmin;
          }
          return token;
      },
      async session({ session, token }) {
          if (session.user) {
              session.user.id = token.sub!;
              session.user.name = token.username as string;

              const customToken = token as any;
              session.user.email = customToken.email;
              session.user.isAdmin = customToken.isAdmin;
          }
          return session;
      },
  },
} satisfies Omit<AuthConfig, "raw">;

We use Auth.js for authentication and JWT for stateless session management. Passwords are never stored in plain text, instead bcrypt is used to hash and verify credentials. Input validation ensures that username and password are strings before any database query is made.

export function getAuthState() {
    const c = ctx();
    const userId = parseSessionUserId(c.session?.user?.id);
    return {
        isLoggedIn: Boolean(userId),
        userId: userId ?? null,
        username: c.session?.user?.name ?? null,
        isAdmin: c.session?.user?.isAdmin,
        session: c.session,
    };
}

export function requireUser() {
    const c = ctx();
    const userId = parseSessionUserId(c.session?.user?.id);
    if (!userId) throw Abort();
    return { c, userId };
}

export async function requireAdmin() {
    const { c, userId } = requireUser();

    if(!c.session?.user?.isAdmin) throw Abort();

    const isAdmin = await drizzleQueries.isAdmin(c.db, userId);
    if (!isAdmin) throw Abort();

    return { c, userId };
}

We also implement strict role-based access control middleware. The requireAdmin function implements a Double-Check Strategy: it verifies the admin flag in the JWT session and performs a real-time database check. This ensures that if an admin's rights are revoked, they lose access immediately, even if their session is still active.

Last modified 11 days ago Last modified on 01/29/26 02:17:55
Note: See TracWiki for help on using the wiki.