File size: 2,045 Bytes
b26189e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { Request } from 'express';
import { ClerkService } from './clerk.service';
import { UsersService } from '../users/users.service';
import { extractBearer } from './optional-auth.guard';

/** Requires a valid Clerk session. Used for personal data endpoints. */
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private readonly clerk: ClerkService,
    private readonly users: UsersService,
    private readonly config: ConfigService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest<Request & { user?: unknown }>();

    // Normal path: a signed-in user supplies a Clerk session token.
    const identity = await this.clerk.verify(extractBearer(req));
    if (identity) {
      const user = await this.users.syncFromClerk(identity);
      if (!user) throw new UnauthorizedException('User could not be resolved');
      req.user = user;
      return true;
    }

    // Trusted server-to-server path: the OMNIA agent scheduler places recurring
    // orders on a user's behalf when they aren't present (no live session).
    // Authenticated by a shared secret header + the target user's clerkId in body.
    if (await this.resolveAgentService(req)) return true;

    throw new UnauthorizedException('Authentication required');
  }

  private async resolveAgentService(req: Request & { user?: unknown }): Promise<boolean> {
    const secret = this.config.get<string>('OMNIA_AGENT_SECRET');
    const provided = req.headers['x-omnia-agent-secret'];
    const body = req.body as { clerkId?: string; email?: string } | undefined;
    const clerkId = body?.clerkId;
    if (!secret || provided !== secret || !clerkId) return false;
    const user = await this.users.findOrCreateByClerkId(clerkId, body?.email);
    if (!user) return false;
    req.user = user;
    return true;
  }
}