Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/mukti-api/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {
Body,
Controller,
Delete,
ForbiddenException,
Get,
HttpCode,
HttpStatus,
Ip,
Logger,
NotFoundException,
Param,
Patch,
Post,
Expand Down Expand Up @@ -55,6 +57,7 @@ import {
} from './dto/auth.swagger';
import { LoginRateLimitGuard } from './guards/login-rate-limit.guard';
import { PasswordResetRateLimitGuard } from './guards/password-reset-rate-limit.guard';
import { WaitlistService } from '../waitlist/waitlist.service';
import { AuthService } from './services/auth.service';
import { OAuthProfile, OAuthService } from './services/oauth.service';
import { SessionService } from './services/session.service';
Expand All @@ -79,6 +82,7 @@ export class AuthController {
private readonly sessionService: SessionService,
private readonly oauthService: OAuthService,
private readonly configService: ConfigService,
private readonly waitlistService: WaitlistService,
) {
this.cookieDomain = this.getCookieDomainFromConfig();
}
Expand Down Expand Up @@ -430,6 +434,21 @@ export class AuthController {
): Promise<AuthResponseDto> {
this.logger.log(`Registration attempt from IP ${ip}`);

const normalizedEmail = dto.email.trim().toLowerCase();
dto.email = normalizedEmail;

// Signups are restricted to emails already present in waitlist.
try {
await this.waitlistService.checkEmail(normalizedEmail);
} catch (error) {
if (error instanceof NotFoundException) {
throw new ForbiddenException(
'Sign up is currently limited to waitlisted emails. Join the waitlist first.',
);
}
throw error;
}

// Register user
const result = await this.authService.register(dto);

Expand Down
2 changes: 2 additions & 0 deletions packages/mukti-api/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '../../schemas/refresh-token.schema';
import { Session, SessionSchema } from '../../schemas/session.schema';
import { User, UserSchema } from '../../schemas/user.schema';
import { WaitlistModule } from '../waitlist/waitlist.module';
import { AuthController } from './auth.controller';
import { EmailVerifiedGuard } from './guards/email-verified.guard';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
Expand Down Expand Up @@ -94,6 +95,7 @@ import { JwtStrategy } from './strategies/jwt.strategy';
]),

ConfigModule,
WaitlistModule,
],
providers: [
AuthService,
Expand Down
57 changes: 47 additions & 10 deletions packages/mukti-web/src/app/(auth)/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { Leaf, ShieldCheck, Sparkles } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useState } from 'react';

Expand All @@ -17,9 +18,9 @@ export default function AuthPage() {
<GradientBackground>
<div
className={cn(
'mx-auto w-full max-w-[95%] xs:max-w-md sm:max-w-lg',
'rounded-3xl border border-japandi-sand/70 bg-japandi-cream/80',
'p-6 sm:p-8 md:p-10',
'mx-auto w-full max-w-[95%] xs:max-w-md sm:max-w-xl',
'rounded-3xl border border-japandi-sand/70 bg-japandi-cream/85',
'p-6 sm:p-8',
'shadow-[0_24px_70px_-42px_rgba(107,77,58,0.45)] dark:shadow-[0_24px_70px_-42px_rgba(0,0,0,0.65)] backdrop-blur-sm',
'animate-pulse'
)}
Expand Down Expand Up @@ -66,27 +67,35 @@ function AuthContent() {

return (
<GradientBackground>
<div className="relative mx-auto w-full max-w-[95%] xs:max-w-md sm:max-w-lg">
<div className="relative mx-auto w-full max-w-[95%] xs:max-w-md sm:max-w-xl">
<div
className={cn(
'relative w-full rounded-3xl border border-japandi-sand/70 bg-japandi-cream/80 backdrop-blur-sm',
'p-6 sm:p-8 md:p-10',
'relative w-full rounded-3xl border border-japandi-sand/70 bg-japandi-cream/85 backdrop-blur-sm',
'p-6 sm:p-8',
'shadow-[0_24px_70px_-42px_rgba(107,77,58,0.45)] dark:shadow-[0_24px_70px_-42px_rgba(0,0,0,0.65)]',
'animate-in fade-in-0 zoom-in-95 duration-300'
)}
suppressHydrationWarning
>
{/* Logo/Title */}
<div className="mb-6 text-center sm:mb-8" suppressHydrationWarning>
<p className="text-japandi-label mb-3 text-japandi-timber/85">Mukti</p>
<div className="mb-6 space-y-4 text-center sm:mb-8" suppressHydrationWarning>
<div className="mx-auto flex w-fit items-center gap-2 rounded-full border border-japandi-sand/75 bg-japandi-cream/70 px-3 py-1.5">
<span className="text-japandi-label text-japandi-timber/85">Mukti</span>
<span className="text-japandi-label text-japandi-stone/45">Socratic Workspace</span>
</div>
<h1 className="text-japandi-heading mb-2 text-2xl sm:text-3xl md:text-[2rem]">
Welcome back
{activeTab === 'signup' ? 'Begin with intention' : 'Welcome back'}
</h1>
<p className="text-japandi-body text-sm text-japandi-stone/75">
{activeTab === 'signup'
? 'Create your account to begin your inquiry practice.'
? 'Create your account with a waitlisted email to enter Mukti.'
: 'Sign in to continue your inquiry journey.'}
</p>
{activeTab === 'signup' && (
<p className="text-xs text-japandi-timber/85">
Sign up is invite-gated through the waitlist.
</p>
)}
</div>

{/* Tab Navigation */}
Expand Down Expand Up @@ -160,6 +169,34 @@ function AuthContent() {
)}
</div>
</div>

<div className="mt-6 grid grid-cols-1 gap-2 border-t border-japandi-sand/70 pt-5 text-left xs:grid-cols-3">
<div className="rounded-lg border border-japandi-sand/60 bg-japandi-light-stone/45 px-3 py-2">
<div className="mb-1 flex items-center gap-2 text-japandi-timber">
<ShieldCheck className="h-3.5 w-3.5" />
<span className="text-japandi-label">Private</span>
</div>
<p className="text-xs text-japandi-stone/70">Session-backed secure authentication.</p>
</div>
<div className="rounded-lg border border-japandi-sand/60 bg-japandi-light-stone/45 px-3 py-2">
<div className="mb-1 flex items-center gap-2 text-japandi-timber">
<Leaf className="h-3.5 w-3.5" />
<span className="text-japandi-label">Calm</span>
</div>
<p className="text-xs text-japandi-stone/70">
Focused space designed for deep inquiry.
</p>
</div>
<div className="rounded-lg border border-japandi-sand/60 bg-japandi-light-stone/45 px-3 py-2">
<div className="mb-1 flex items-center gap-2 text-japandi-timber">
<Sparkles className="h-3.5 w-3.5" />
<span className="text-japandi-label">Guided</span>
</div>
<p className="text-xs text-japandi-stone/70">
Socratic prompts instead of instant answers.
</p>
</div>
</div>
</div>
</div>
</GradientBackground>
Expand Down
18 changes: 15 additions & 3 deletions packages/mukti-web/src/components/auth/sign-up-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ export function SignUpForm({ onSuccess, onSwitchToSignIn }: SignUpFormProps) {
}
};

const registrationErrorMessage =
registerMutation.error instanceof Error
? registerMutation.error.message
: 'Registration failed. Please try again.';
const waitlistRestrictionError = registrationErrorMessage.toLowerCase().includes('waitlist');

return (
<Form {...form}>
<form className="space-y-4 sm:space-y-5" onSubmit={form.handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -236,10 +242,16 @@ export function SignUpForm({ onSuccess, onSwitchToSignIn }: SignUpFormProps) {
{registerMutation.error && (
<div className="rounded-xl border border-red-500/30 bg-red-500/10 p-2.5 sm:p-3">
<p className="text-xs text-red-700 dark:text-red-300 sm:text-sm">
{registerMutation.error instanceof Error
? registerMutation.error.message
: 'Registration failed. Please try again.'}
{registrationErrorMessage}
</p>
{waitlistRestrictionError && (
<a
className="mt-1 inline-block text-xs font-medium text-japandi-timber underline underline-offset-2 hover:text-japandi-terracotta"
href="/#join"
>
Join the waitlist for access
</a>
)}
</div>
)}

Expand Down
Loading