Complete SaaS self-service onboarding sprint: - Stripe-powered signup flow: pricing page → checkout → provisioning → activation - Refresh token infrastructure: 1h access tokens + 30-day httpOnly cookie refresh - TOTP MFA with QR setup, recovery codes, and login challenge flow - Google + Azure AD SSO (conditional on env vars) with account linking - WebAuthn passkey registration and passwordless login - Guided onboarding checklist with server-side progress tracking - Stubbed email service (console + DB logging, ready for real provider) - Settings page with tabbed security settings (MFA, passkeys, linked accounts) - Login page enhanced with MFA verification, SSO buttons, passkey login - Database migration 015 with all new tables and columns - Version bump to 2026.03.17 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
32 lines
1.3 KiB
TypeScript
32 lines
1.3 KiB
TypeScript
import { Controller, Get, Patch, Body, UseGuards, Request, BadRequestException } from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
import { AllowViewer } from '../../common/decorators/allow-viewer.decorator';
|
|
import { OnboardingService } from './onboarding.service';
|
|
|
|
@ApiTags('onboarding')
|
|
@Controller('onboarding')
|
|
@ApiBearerAuth()
|
|
@UseGuards(JwtAuthGuard)
|
|
export class OnboardingController {
|
|
constructor(private onboardingService: OnboardingService) {}
|
|
|
|
@Get('progress')
|
|
@ApiOperation({ summary: 'Get onboarding progress for current org' })
|
|
@AllowViewer()
|
|
async getProgress(@Request() req: any) {
|
|
const orgId = req.user.orgId;
|
|
if (!orgId) throw new BadRequestException('No organization context');
|
|
return this.onboardingService.getProgress(orgId);
|
|
}
|
|
|
|
@Patch('progress')
|
|
@ApiOperation({ summary: 'Mark an onboarding step as complete' })
|
|
async markStep(@Request() req: any, @Body() body: { step: string }) {
|
|
const orgId = req.user.orgId;
|
|
if (!orgId) throw new BadRequestException('No organization context');
|
|
if (!body.step) throw new BadRequestException('step is required');
|
|
return this.onboardingService.markStepComplete(orgId, body.step);
|
|
}
|
|
}
|