Skip to content

Commit f162850

Browse files
committed
Verify logged-in at interceptor for all mutations unless the decorator overrides
Before we required that the resolver had a parameter ```ts @LoggedInSession() session: Session ``` Which was clunky because we had to have it even if we didn't use it. We almost always needed to pass it down, so it made sense. Now, with the session moving to an ALS, we will rarely need it. So this moves the verification to the interceptor to be run regardless of handler parameters.
1 parent 05de326 commit f162850

File tree

7 files changed

+34
-1
lines changed

7 files changed

+34
-1
lines changed

src/common/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ export class SessionPipe implements PipeTransform {
4949
}
5050
}
5151

52+
/** @deprecated */
5253
export const LoggedInSession = () =>
5354
AnonSession({ transform: loggedInSession });
5455

56+
/** @deprecated */
5557
export const AnonSession =
5658
(...pipes: Array<Type<PipeTransform> | PipeTransform>): ParameterDecorator =>
5759
(...args) => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createMetadataDecorator } from '@seedcompany/nest';
2+
3+
export const LoggedIn = () => Anonymous(false);
4+
export const Anonymous = createMetadataDecorator({
5+
setter: (anonymous = true) => anonymous,
6+
types: ['class', 'method'],
7+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './authentication.service';
22
export { SessionHost } from './session.host';
3+
export * from './anonymous.decorator';

src/components/authentication/login.resolver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Privileges } from '../authorization';
1212
import { Power } from '../authorization/dto';
1313
import { UserLoader } from '../user';
1414
import { User } from '../user/dto';
15+
import { Anonymous } from './anonymous.decorator';
1516
import { AuthenticationService } from './authentication.service';
1617
import { LoginInput, LoginOutput, LogoutOutput } from './dto';
1718

@@ -28,6 +29,7 @@ export class LoginResolver {
2829
@sensitive-secrets
2930
`,
3031
})
32+
@Anonymous()
3133
async login(
3234
@Args('input') input: LoginInput,
3335
@AnonSession() session: Session,
@@ -43,6 +45,7 @@ export class LoginResolver {
4345
@sensitive-secrets
4446
`,
4547
})
48+
@Anonymous()
4649
async logout(@AnonSession() session: Session): Promise<LogoutOutput> {
4750
await this.authentication.logout(session.token);
4851
await this.authentication.refreshCurrentSession(); // ensure session data is fresh

src/components/authentication/password.resolver.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Args, Mutation, Resolver } from '@nestjs/graphql';
22
import { stripIndent } from 'common-tags';
33
import { AnonSession, LoggedInSession, type Session } from '~/common';
4+
import { Anonymous } from './anonymous.decorator';
45
import { AuthenticationService } from './authentication.service';
56
import {
67
ChangePasswordArgs,
@@ -45,6 +46,7 @@ export class PasswordResolver {
4546
@sensitive-secrets
4647
`,
4748
})
49+
@Anonymous()
4850
async resetPassword(
4951
@Args('input') input: ResetPasswordInput,
5052
@AnonSession() session: Session,

src/components/authentication/register.resolver.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Privileges } from '../authorization';
1212
import { Power } from '../authorization/dto';
1313
import { UserLoader } from '../user';
1414
import { User } from '../user/dto';
15+
import { Anonymous } from './anonymous.decorator';
1516
import { AuthenticationService } from './authentication.service';
1617
import { RegisterInput, RegisterOutput } from './dto';
1718

@@ -28,6 +29,7 @@ export class RegisterResolver {
2829
@sensitive-secrets
2930
`,
3031
})
32+
@Anonymous()
3133
async register(
3234
@Args('input') input: RegisterInput,
3335
@AnonSession() session: Session,

src/components/authentication/session.interceptor.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type NestInterceptor,
88
} from '@nestjs/common';
99
import { GqlExecutionContext } from '@nestjs/graphql';
10-
import { csv } from '@seedcompany/common';
10+
import { csv, type FnLike } from '@seedcompany/common';
1111
import { AsyncLocalStorage } from 'async_hooks';
1212
import { BehaviorSubject } from 'rxjs';
1313
import {
@@ -21,9 +21,11 @@ import {
2121
type Session,
2222
UnauthenticatedException,
2323
} from '~/common';
24+
import { loggedInSession as verifyLoggedIn } from '~/common/session';
2425
import { ConfigService } from '~/core';
2526
import { GlobalHttpHook, type IRequest } from '~/core/http';
2627
import { rolesForScope } from '../authorization/dto';
28+
import { Anonymous } from './anonymous.decorator';
2729
import { AuthenticationService } from './authentication.service';
2830
import { SessionHost } from './session.host';
2931

@@ -64,14 +66,28 @@ export class SessionInterceptor implements NestInterceptor {
6466

6567
const type = executionContext.getType();
6668

69+
let isMutation = true;
6770
let session;
6871
if (type === 'graphql') {
72+
const gqlExecutionContext = GqlExecutionContext.create(executionContext);
73+
const op = gqlExecutionContext.getInfo().operation;
74+
isMutation = op.operation === 'mutation';
6975
session = await this.handleGql(executionContext);
7076
} else if (type === 'http') {
77+
const request = executionContext.switchToHttp().getRequest();
78+
isMutation = request.method !== 'GET' && request.method !== 'HEAD';
7179
session = await this.handleHttp(executionContext);
7280
}
7381
session$.next(session);
7482

83+
const allowAnonymous =
84+
Anonymous.get(executionContext.getHandler() as FnLike) ??
85+
Anonymous.get(executionContext.getClass()) ??
86+
!isMutation;
87+
if (!allowAnonymous && session) {
88+
verifyLoggedIn(session);
89+
}
90+
7591
return next.handle();
7692
}
7793

0 commit comments

Comments
 (0)