Browse Source

feat: authguard for password change api

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/5444/head
Pranav C 1 year ago
parent
commit
973a7e67ba
  1. 28
      packages/nocodb-nest/src/app.module.ts
  2. 7
      packages/nocodb-nest/src/guards/global/global.guard.spec.ts
  3. 36
      packages/nocodb-nest/src/guards/global/global.guard.ts
  4. 33
      packages/nocodb-nest/src/modules/global/global.module.ts
  5. 5
      packages/nocodb-nest/src/modules/users/users.controller.ts
  6. 6
      packages/nocodb-nest/src/modules/users/users.module.ts
  7. 2
      packages/nocodb-nest/src/modules/users/users.service.ts
  8. 4
      packages/nocodb-nest/src/modules/views/views.controller.ts
  9. 10
      packages/nocodb-nest/src/strategies/authtoken.strategy/authtoken.strategy.ts
  10. 65
      packages/nocodb-nest/src/strategies/jwt.strategy.ts

28
packages/nocodb-nest/src/app.module.ts

@ -1,12 +1,13 @@
import { Module, RequestMethod } from '@nestjs/common'; import { Module, RequestMethod } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core'; import { APP_FILTER, APP_GUARD } from '@nestjs/core';
import { ExtractJwt } from 'passport-jwt'; import { ExtractJwt } from 'passport-jwt';
import { Connection } from './connection/connection'; import { Connection } from './connection/connection';
import { GlobalExceptionFilter } from './filters/global-exception/global-exception.filter'; import { GlobalExceptionFilter } from './filters/global-exception/global-exception.filter';
import { GlobalGuard } from './guards/global/global.guard';
import { GlobalMiddleware } from './middlewares/global/global.middleware'; import { GlobalMiddleware } from './middlewares/global/global.middleware';
import { AuthModule } from './modules/auth/auth.module'; import { AuthModule } from './modules/auth/auth.module';
import { ExtractProjectIdMiddleware } from './middlewares/extract-project-id/extract-project-id.middleware'; import { ExtractProjectIdMiddleware } from './middlewares/extract-project-id/extract-project-id.middleware';
import { AuthService } from './modules/auth/auth.service' import { AuthService } from './modules/auth/auth.service';
import { UsersModule } from './modules/users/users.module'; import { UsersModule } from './modules/users/users.module';
import { MetaService } from './meta/meta.service'; import { MetaService } from './meta/meta.service';
import { UsersService } from './modules/users/users.service'; import { UsersService } from './modules/users/users.service';
@ -51,23 +52,24 @@ import { CachesModule } from './modules/caches/caches.module';
import { TestModule } from './modules/test/test.module'; import { TestModule } from './modules/test/test.module';
import { PluginsModule } from './modules/plugins/plugins.module'; import { PluginsModule } from './modules/plugins/plugins.module';
import { GlobalModule } from './modules/global/global.module'; import { GlobalModule } from './modules/global/global.module';
import { LocalStrategy } from './strategies/local.strategy' import { LocalStrategy } from './strategies/local.strategy';
import NcConfigFactory from './utils/NcConfigFactory' import NcConfigFactory from './utils/NcConfigFactory';
import NcUpgrader from './version-upgrader/NcUpgrader'; import NcUpgrader from './version-upgrader/NcUpgrader';
import { ClientService } from './services/client/client.service';
import { AuthTokenStrategy } from './strategies/authtoken.strategy/authtoken.strategy';
import { BaseViewStrategy } from './strategies/base-view.strategy/base-view.strategy';
import { GoogleStrategy } from './strategies/google.strategy/google.strategy';
import type { import type {
MiddlewareConsumer, MiddlewareConsumer,
OnApplicationBootstrap, OnApplicationBootstrap,
Provider, Provider,
} from '@nestjs/common'; } from '@nestjs/common';
import { ClientService } from './services/client/client.service'; /*
import { AuthTokenStrategy } from './strategies/authtoken.strategy/authtoken.strategy';
import { BaseViewStrategy } from './strategies/base-view.strategy/base-view.strategy';
import { GoogleStrategy } from './strategies/google.strategy/google.strategy';
export const JwtStrategyProvider: Provider = { export const JwtStrategyProvider: Provider = {
provide: JwtStrategy, provide: JwtStrategy,
useFactory: async (usersService: UsersService) => { useFactory: async (usersService: UsersService) => {
const config = await NcConfigFactory.make() const config = await NcConfigFactory.make();
const options = { const options = {
// ignoreExpiration: false, // ignoreExpiration: false,
@ -82,6 +84,7 @@ export const JwtStrategyProvider: Provider = {
}, },
inject: [UsersService], inject: [UsersService],
}; };
*/
@Module({ @Module({
imports: [ imports: [
@ -129,18 +132,23 @@ export const JwtStrategyProvider: Provider = {
], ],
controllers: [], controllers: [],
providers: [ providers: [
// {
// provide: APP_GUARD,
// useClass: GlobalGuard,
// },
AuthService, AuthService,
{ {
provide: APP_FILTER, provide: APP_FILTER,
useClass: GlobalExceptionFilter, useClass: GlobalExceptionFilter,
}, },
JwtStrategyProvider, // JwtStrategyProvider,
LocalStrategy, LocalStrategy,
ExtractProjectIdMiddleware, ExtractProjectIdMiddleware,
ClientService, ClientService,
AuthTokenStrategy, AuthTokenStrategy,
BaseViewStrategy, BaseViewStrategy,
GoogleStrategy, GoogleStrategy,
// GlobalGuard,
], ],
}) })
export class AppModule implements OnApplicationBootstrap { export class AppModule implements OnApplicationBootstrap {

7
packages/nocodb-nest/src/guards/global/global.guard.spec.ts

@ -0,0 +1,7 @@
import { GlobalGuard } from './global.guard';
describe('GlobalGuard', () => {
it('should be defined', () => {
expect(new GlobalGuard()).toBeDefined();
});
});

36
packages/nocodb-nest/src/guards/global/global.guard.ts

@ -0,0 +1,36 @@
import { Inject, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { JwtStrategy } from '../../strategies/jwt.strategy';
import type { ExecutionContext } from '@nestjs/common';
@Injectable()
export class GlobalGuard extends AuthGuard(['jwt']) {
constructor(private jwtStrategy: JwtStrategy) {
super();
}
async canActivate(context: ExecutionContext) {
let result;
try {
result = (await super.canActivate(context)) as boolean;
} catch (e) {
console.log(e);
}
if (!result) {
// If JWT authentication fails, use the fallback strategy to set a default user
const req = context.switchToHttp().getRequest();
const user = await this.fallbackAuthenticate(req);
req.user = user;
return true;
}
return true;
}
private async fallbackAuthenticate(req: any): Promise<any> {
return this.jwtStrategy.validate(req, {
roles: {
guest: true,
},
});
}
}

33
packages/nocodb-nest/src/modules/global/global.module.ts

@ -1,8 +1,33 @@
import { Global, Module } from '@nestjs/common' import { Global, Module, Provider } from '@nestjs/common'
import { JwtModule, JwtService } from '@nestjs/jwt' import { JwtModule, JwtService } from '@nestjs/jwt'
import { ExtractJwt } from 'passport-jwt'
import { Connection } from '../../connection/connection' import { Connection } from '../../connection/connection'
import { GlobalGuard } from '../../guards/global/global.guard'
import { MetaService } from '../../meta/meta.service' import { MetaService } from '../../meta/meta.service'
import { JwtStrategy } from '../../strategies/jwt.strategy'
import NcConfigFactory from '../../utils/NcConfigFactory'
import { jwtConstants } from '../auth/constants' import { jwtConstants } from '../auth/constants'
import { UsersModule } from '../users/users.module'
import { UsersService } from '../users/users.service'
export const JwtStrategyProvider: Provider = {
provide: JwtStrategy,
useFactory: async (usersService: UsersService) => {
const config = await NcConfigFactory.make();
const options = {
// ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromHeader('xc-auth'),
// expiresIn: '10h',
passReqToCallback: true,
secretOrKey: config.auth.jwt.secret,
...config.auth.jwt.options,
};
return new JwtStrategy(options, usersService);
},
inject: [UsersService],
};
@Global() @Global()
@Module({ @Module({
@ -12,11 +37,17 @@ import { jwtConstants } from '../auth/constants'
providers: [ providers: [
Connection, Connection,
MetaService, MetaService,
UsersService,
JwtStrategyProvider,
GlobalGuard
], ],
exports: [ exports: [
Connection, Connection,
MetaService, MetaService,
// JwtService, // JwtService,
JwtStrategyProvider,
UsersService,
GlobalGuard
], ],
}) })
export class GlobalModule { export class GlobalModule {

5
packages/nocodb-nest/src/modules/users/users.controller.ts

@ -12,6 +12,7 @@ import {
} from '@nestjs/common' } from '@nestjs/common'
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../../guards/global/global.guard'
import { NcError } from '../../helpers/catchError'; import { NcError } from '../../helpers/catchError';
import { import {
Acl, Acl,
@ -142,7 +143,7 @@ export class UsersController {
} }
@Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me']) @Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me'])
@UseGuards(ExtractProjectIdMiddleware, AuthGuard('jwt')) @UseGuards(ExtractProjectIdMiddleware, GlobalGuard)
async me(@Request() req) { async me(@Request() req) {
const user = { const user = {
...req.user, ...req.user,
@ -156,7 +157,9 @@ export class UsersController {
'/api/v1/db/auth/password/change', '/api/v1/db/auth/password/change',
'/api/v1/auth/password/change', '/api/v1/auth/password/change',
]) ])
@UseGuards(GlobalGuard)
@Acl('passwordChange') @Acl('passwordChange')
@HttpCode(200)
async passwordChange(@Request() req: any, @Body() body: any): Promise<any> { async passwordChange(@Request() req: any, @Body() body: any): Promise<any> {
if (!(req as any).isAuthenticated()) { if (!(req as any).isAuthenticated()) {
NcError.forbidden('Not allowed'); NcError.forbidden('Not allowed');

6
packages/nocodb-nest/src/modules/users/users.module.ts

@ -7,11 +7,7 @@ import { UsersController } from './users.controller';
@Module({ @Module({
imports: [ imports: [
GlobalModule, GlobalModule
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '10h' },
}),
], ],
controllers: [UsersController], controllers: [UsersController],
providers: [UsersService], providers: [UsersService],

2
packages/nocodb-nest/src/modules/users/users.service.ts

@ -1,6 +1,5 @@
import { promisify } from 'util'; import { promisify } from 'util';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { OrgUserRoles, validatePassword } from 'nocodb-sdk'; import { OrgUserRoles, validatePassword } from 'nocodb-sdk';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { isEmail } from 'validator'; import { isEmail } from 'validator';
@ -28,7 +27,6 @@ import type {
export class UsersService { export class UsersService {
constructor( constructor(
private metaService: MetaService, private metaService: MetaService,
private jwtService: JwtService,
) {} ) {}
async findOne(email: string) { async findOne(email: string) {

4
packages/nocodb-nest/src/modules/views/views.controller.ts

@ -10,17 +10,17 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ViewUpdateReqType } from 'nocodb-sdk'; import { ViewUpdateReqType } from 'nocodb-sdk';
import { PagedResponseImpl } from '../../helpers/PagedResponse'; import { PagedResponseImpl } from '../../helpers/PagedResponse';
import { import {
ExtractProjectIdMiddleware, ExtractProjectIdMiddleware,
UseAclMiddleware, UseAclMiddleware,
} from '../../middlewares/extract-project-id/extract-project-id.middleware'; } from '../../middlewares/extract-project-id/extract-project-id.middleware';
import { GlobalGuard } from '../../guards/global/global.guard';
import { ViewsService } from './views.service'; import { ViewsService } from './views.service';
@Controller() @Controller()
@UseGuards(ExtractProjectIdMiddleware, AuthGuard('jwt')) @UseGuards(ExtractProjectIdMiddleware, GlobalGuard)
export class ViewsController { export class ViewsController {
constructor(private readonly viewsService: ViewsService) {} constructor(private readonly viewsService: ViewsService) {}

10
packages/nocodb-nest/src/strategies/authtoken.strategy/authtoken.strategy.ts

@ -7,10 +7,12 @@ import type { Request } from 'express';
@Injectable() @Injectable()
export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') { export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
constructor() { constructor() {
super({ super(
headerFields: ['xc-token'], {
passReqToCallback: true, headerFields: ['xc-token'],
}); passReqToCallback: true,
},
);
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types

65
packages/nocodb-nest/src/strategies/jwt.strategy.ts

@ -1,16 +1,16 @@
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable, UnauthorizedException } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt'
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk'
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache'
import { ProjectUser, User } from '../models'; import { ProjectUser, User } from '../models'
import { genJwt } from '../modules/users/helpers'; import { genJwt } from '../modules/users/helpers'
import Noco from '../Noco'; import Noco from '../Noco'
import extractRolesObj from '../utils/extractRolesObj'; import extractRolesObj from '../utils/extractRolesObj'
import { CacheGetType, CacheScope } from '../utils/globals'; import { CacheGetType, CacheScope } from '../utils/globals'
import { jwtConstants } from '../modules/auth/constants'; import { jwtConstants } from '../modules/auth/constants'
import { UsersService } from '../modules/users/users.service'; import { UsersService } from '../modules/users/users.service'
import NcConfigFactory from '../utils/NcConfigFactory'; import NcConfigFactory from '../utils/NcConfigFactory'
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { export class JwtStrategy extends PassportStrategy(Strategy) {
@ -18,10 +18,13 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
super({ super({
expiresIn: '10h', expiresIn: '10h',
...options, ...options,
}); })
} }
async validate(req: any, jwtPayload: any) { async validate(req: any, jwtPayload: any) {
if (!jwtPayload?.email) return jwtPayload
// todo: improve this // todo: improve this
if ( if (
req.ncProjectId && req.ncProjectId &&
@ -31,19 +34,19 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
return { return {
...user, ...user,
roles: extractRolesObj(`owner,creator,${OrgUserRoles.SUPER_ADMIN}`), roles: extractRolesObj(`owner,creator,${OrgUserRoles.SUPER_ADMIN}`),
}; }
}); })
} }
const keyVals = [jwtPayload?.email]; const keyVals = [jwtPayload?.email]
if (req.ncProjectId) { if (req.ncProjectId) {
keyVals.push(req.ncProjectId); keyVals.push(req.ncProjectId)
} }
const key = keyVals.join('___'); const key = keyVals.join('___')
const cachedVal = await NocoCache.get( const cachedVal = await NocoCache.get(
`${CacheScope.USER}:${key}`, `${CacheScope.USER}:${key}`,
CacheGetType.TYPE_OBJECT, CacheGetType.TYPE_OBJECT,
); )
if (cachedVal) { if (cachedVal) {
/*todo: tobe fixed /*todo: tobe fixed
@ -54,12 +57,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
) { ) {
throw new Error('Token Expired. Please login again.'); throw new Error('Token Expired. Please login again.');
}*/ }*/
return cachedVal; return cachedVal
} }
return User.getByEmail(jwtPayload?.email).then( return User.getByEmail(jwtPayload?.email).then(
async (user: { roles: any; id: string }) => { async (user: { roles: any; id: string }) => {
user.roles = extractRolesObj(user?.roles); user.roles = extractRolesObj(user?.roles)
/* /*
todo: tobe fixed todo: tobe fixed
if ( if (
@ -77,26 +80,26 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
return ProjectUser.get(req.ncProjectId, user.id).then( return ProjectUser.get(req.ncProjectId, user.id).then(
async (projectUser) => { async (projectUser) => {
user.roles = extractRolesObj(projectUser?.roles || user.roles); user.roles = extractRolesObj(projectUser?.roles || user.roles)
user.roles = extractRolesObj( user.roles = extractRolesObj(
user.roles === 'owner' ? 'owner,creator' : user.roles, user.roles === 'owner' ? 'owner,creator' : user.roles,
); )
// + (user.roles ? `,${user.roles}` : ''); // + (user.roles ? `,${user.roles}` : '');
await NocoCache.set(`${CacheScope.USER}:${key}`, user); await NocoCache.set(`${CacheScope.USER}:${key}`, user)
return user; return user
}, },
); )
} else { } else {
// const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true}; // const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true};
if (user) { if (user) {
await NocoCache.set(`${CacheScope.USER}:${key}`, user); await NocoCache.set(`${CacheScope.USER}:${key}`, user)
return user; return user
} else { } else {
throw new Error('User not found'); throw new Error('User not found')
} }
} }
}, },
); )
} }
} }

Loading…
Cancel
Save