62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|||
|
|
import { PassportStrategy } from '@nestjs/passport';
|
||
|
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
||
|
|
import { customerJwtSecret } from './customer-auth.service';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Marks a token as a customer token. Checked on every customer route, so an
|
||
|
|
* admin token — even one signed with the same secret by misconfiguration —
|
||
|
|
* cannot be used to read customer data (T1).
|
||
|
|
*/
|
||
|
|
export const CUSTOMER_TOKEN_TYPE = 'customer' as const;
|
||
|
|
|
||
|
|
export interface CustomerJwtPayload {
|
||
|
|
sub: string;
|
||
|
|
mobile: string;
|
||
|
|
typ: typeof CUSTOMER_TOKEN_TYPE;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** What `@CurrentCustomer()` hands a handler. */
|
||
|
|
export interface AuthenticatedCustomer {
|
||
|
|
id: string;
|
||
|
|
mobile: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The customer principal, kept entirely separate from JwtStrategy (which reads
|
||
|
|
* AdminUser). The reference had the same split: `auth:api` for admins and
|
||
|
|
* `auth('api-customer')` for the app.
|
||
|
|
*/
|
||
|
|
@Injectable()
|
||
|
|
export class CustomerJwtStrategy extends PassportStrategy(Strategy, 'customer-jwt') {
|
||
|
|
constructor(private readonly prisma: PrismaService) {
|
||
|
|
super({
|
||
|
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||
|
|
ignoreExpiration: false,
|
||
|
|
secretOrKey: customerJwtSecret(),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async validate(payload: CustomerJwtPayload): Promise<AuthenticatedCustomer> {
|
||
|
|
// A token that is not explicitly a customer token is refused, whatever else
|
||
|
|
// it carries.
|
||
|
|
if (payload?.typ !== CUSTOMER_TOKEN_TYPE) {
|
||
|
|
throw new UnauthorizedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Re-read on every request, so deactivating or archiving a customer takes
|
||
|
|
// effect immediately instead of at token expiry.
|
||
|
|
const customer = await this.prisma.customer.findUnique({
|
||
|
|
where: { id: payload.sub },
|
||
|
|
select: { id: true, mobile: true, isActive: true, isArchived: true },
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!customer || !customer.isActive || customer.isArchived) {
|
||
|
|
throw new UnauthorizedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
return { id: customer.id, mobile: customer.mobile };
|
||
|
|
}
|
||
|
|
}
|