feat: nestjs config package added

This commit is contained in:
Mir Arif Hasan
2023-11-24 16:13:59 +06:00
parent 49c2ee0d38
commit 5bee3471c5
13 changed files with 798 additions and 551 deletions

View File

@@ -28,6 +28,7 @@
"@nestjs-modules/mailer": "^1.9.1", "@nestjs-modules/mailer": "^1.9.1",
"@nestjs/apollo": "^12.0.9", "@nestjs/apollo": "^12.0.9",
"@nestjs/common": "^10.2.6", "@nestjs/common": "^10.2.6",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.2.6", "@nestjs/core": "^10.2.6",
"@nestjs/graphql": "^12.0.9", "@nestjs/graphql": "^12.0.9",
"@nestjs/jwt": "^10.1.1", "@nestjs/jwt": "^10.1.1",

View File

@@ -25,6 +25,7 @@ import { TeamEnvironmentsService } from '../team-environments/team-environments.
import { TeamInvitationService } from '../team-invitation/team-invitation.service'; import { TeamInvitationService } from '../team-invitation/team-invitation.service';
import { TeamMemberRole } from '../team/team.model'; import { TeamMemberRole } from '../team/team.model';
import { ShortcodeService } from 'src/shortcode/shortcode.service'; import { ShortcodeService } from 'src/shortcode/shortcode.service';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
@@ -39,6 +40,7 @@ export class AdminService {
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly mailerService: MailerService, private readonly mailerService: MailerService,
private readonly shortcodeService: ShortcodeService, private readonly shortcodeService: ShortcodeService,
private readonly configService: ConfigService,
) {} ) {}
/** /**
@@ -79,7 +81,7 @@ export class AdminService {
template: 'user-invitation', template: 'user-invitation',
variables: { variables: {
inviteeEmail: inviteeEmail, inviteeEmail: inviteeEmail,
magicLink: `${process.env.VITE_BASE_URL}`, magicLink: `${this.configService.get('VITE_BASE_URL')}`,
}, },
}); });
} catch (e) { } catch (e) {

View File

@@ -20,49 +20,62 @@ import { ShortcodeModule } from './shortcode/shortcode.module';
import { COOKIES_NOT_FOUND } from './errors'; import { COOKIES_NOT_FOUND } from './errors';
import { ThrottlerModule } from '@nestjs/throttler'; import { ThrottlerModule } from '@nestjs/throttler';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ @Module({
imports: [ imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({ ConfigModule.forRoot({
buildSchemaOptions: { isGlobal: true,
numberScalarMode: 'integer',
},
playground: process.env.PRODUCTION !== 'true',
autoSchemaFile: true,
installSubscriptionHandlers: true,
subscriptions: {
'subscriptions-transport-ws': {
path: '/graphql',
onConnect: (_, websocket) => {
try {
const cookies = subscriptionContextCookieParser(
websocket.upgradeReq.headers.cookie,
);
return {
headers: { ...websocket?.upgradeReq?.headers, cookies },
};
} catch (error) {
throw new HttpException(COOKIES_NOT_FOUND, 400, {
cause: new Error(COOKIES_NOT_FOUND),
});
}
},
},
},
context: ({ req, res, connection }) => ({
req,
res,
connection,
}),
driver: ApolloDriver,
}), }),
ThrottlerModule.forRoot([ GraphQLModule.forRootAsync<ApolloDriverConfig>({
{ driver: ApolloDriver,
ttl: +process.env.RATE_LIMIT_TTL, imports: [ConfigModule],
limit: +process.env.RATE_LIMIT_MAX, inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
return {
buildSchemaOptions: {
numberScalarMode: 'integer',
},
playground: configService.get('PRODUCTION') !== 'true',
autoSchemaFile: true,
installSubscriptionHandlers: true,
subscriptions: {
'subscriptions-transport-ws': {
path: '/graphql',
onConnect: (_, websocket) => {
try {
const cookies = subscriptionContextCookieParser(
websocket.upgradeReq.headers.cookie,
);
return {
headers: { ...websocket?.upgradeReq?.headers, cookies },
};
} catch (error) {
throw new HttpException(COOKIES_NOT_FOUND, 400, {
cause: new Error(COOKIES_NOT_FOUND),
});
}
},
},
},
context: ({ req, res, connection }) => ({
req,
res,
connection,
}),
};
}, },
]), }),
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => [
{
ttl: +configService.get('RATE_LIMIT_TTL'),
limit: +configService.get('RATE_LIMIT_MAX'),
},
],
}),
UserModule, UserModule,
AuthModule, AuthModule,
AdminModule, AdminModule,

View File

@@ -12,6 +12,7 @@ import { GoogleStrategy } from './strategies/google.strategy';
import { GithubStrategy } from './strategies/github.strategy'; import { GithubStrategy } from './strategies/github.strategy';
import { MicrosoftStrategy } from './strategies/microsoft.strategy'; import { MicrosoftStrategy } from './strategies/microsoft.strategy';
import { AuthProvider, authProviderCheck } from './helper'; import { AuthProvider, authProviderCheck } from './helper';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ @Module({
imports: [ imports: [
@@ -19,8 +20,12 @@ import { AuthProvider, authProviderCheck } from './helper';
UserModule, UserModule,
MailerModule, MailerModule,
PassportModule, PassportModule,
JwtModule.register({ JwtModule.registerAsync({
secret: process.env.JWT_SECRET, imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
}),
}), }),
], ],
providers: [ providers: [

View File

@@ -28,6 +28,7 @@ import { AuthError } from 'src/types/AuthError';
import { AuthUser, IsAdmin } from 'src/types/AuthUser'; import { AuthUser, IsAdmin } from 'src/types/AuthUser';
import { VerificationToken } from '@prisma/client'; import { VerificationToken } from '@prisma/client';
import { Origin } from './helper'; import { Origin } from './helper';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@@ -36,6 +37,7 @@ export class AuthService {
private prismaService: PrismaService, private prismaService: PrismaService,
private jwtService: JwtService, private jwtService: JwtService,
private readonly mailerService: MailerService, private readonly mailerService: MailerService,
private readonly configService: ConfigService,
) {} ) {}
/** /**
@@ -46,10 +48,12 @@ export class AuthService {
*/ */
private async generateMagicLinkTokens(user: AuthUser) { private async generateMagicLinkTokens(user: AuthUser) {
const salt = await bcrypt.genSalt( const salt = await bcrypt.genSalt(
parseInt(process.env.TOKEN_SALT_COMPLEXITY), parseInt(this.configService.get('TOKEN_SALT_COMPLEXITY')),
); );
const expiresOn = DateTime.now() const expiresOn = DateTime.now()
.plus({ hours: parseInt(process.env.MAGIC_LINK_TOKEN_VALIDITY) }) .plus({
hours: parseInt(this.configService.get('MAGIC_LINK_TOKEN_VALIDITY')),
})
.toISO() .toISO()
.toString(); .toString();
@@ -95,13 +99,13 @@ export class AuthService {
*/ */
private async generateRefreshToken(userUid: string) { private async generateRefreshToken(userUid: string) {
const refreshTokenPayload: RefreshTokenPayload = { const refreshTokenPayload: RefreshTokenPayload = {
iss: process.env.VITE_BASE_URL, iss: this.configService.get('VITE_BASE_URL'),
sub: userUid, sub: userUid,
aud: [process.env.VITE_BASE_URL], aud: [this.configService.get('VITE_BASE_URL')],
}; };
const refreshToken = await this.jwtService.sign(refreshTokenPayload, { const refreshToken = await this.jwtService.sign(refreshTokenPayload, {
expiresIn: process.env.REFRESH_TOKEN_VALIDITY, //7 Days expiresIn: this.configService.get('REFRESH_TOKEN_VALIDITY'), //7 Days
}); });
const refreshTokenHash = await argon2.hash(refreshToken); const refreshTokenHash = await argon2.hash(refreshToken);
@@ -127,9 +131,9 @@ export class AuthService {
*/ */
async generateAuthTokens(userUid: string) { async generateAuthTokens(userUid: string) {
const accessTokenPayload: AccessTokenPayload = { const accessTokenPayload: AccessTokenPayload = {
iss: process.env.VITE_BASE_URL, iss: this.configService.get('VITE_BASE_URL'),
sub: userUid, sub: userUid,
aud: [process.env.VITE_BASE_URL], aud: [this.configService.get('VITE_BASE_URL')],
}; };
const refreshToken = await this.generateRefreshToken(userUid); const refreshToken = await this.generateRefreshToken(userUid);
@@ -137,7 +141,7 @@ export class AuthService {
return E.right(<AuthTokens>{ return E.right(<AuthTokens>{
access_token: await this.jwtService.sign(accessTokenPayload, { access_token: await this.jwtService.sign(accessTokenPayload, {
expiresIn: process.env.ACCESS_TOKEN_VALIDITY, //1 Day expiresIn: this.configService.get('ACCESS_TOKEN_VALIDITY'), //1 Day
}), }),
refresh_token: refreshToken.right, refresh_token: refreshToken.right,
}); });
@@ -218,14 +222,14 @@ export class AuthService {
let url: string; let url: string;
switch (origin) { switch (origin) {
case Origin.ADMIN: case Origin.ADMIN:
url = process.env.VITE_ADMIN_URL; url = this.configService.get('VITE_ADMIN_URL');
break; break;
case Origin.APP: case Origin.APP:
url = process.env.VITE_BASE_URL; url = this.configService.get('VITE_BASE_URL');
break; break;
default: default:
// if origin is invalid by default set URL to Hoppscotch-App // if origin is invalid by default set URL to Hoppscotch-App
url = process.env.VITE_BASE_URL; url = this.configService.get('VITE_BASE_URL');
} }
await this.mailerService.sendEmail(email, { await this.mailerService.sendEmail(email, {

View File

@@ -5,18 +5,20 @@ import { AuthService } from '../auth.service';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class GithubStrategy extends PassportStrategy(Strategy) { export class GithubStrategy extends PassportStrategy(Strategy) {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private usersService: UserService, private usersService: UserService,
private configService: ConfigService,
) { ) {
super({ super({
clientID: process.env.GITHUB_CLIENT_ID, clientID: configService.get('GITHUB_CLIENT_ID'),
clientSecret: process.env.GITHUB_CLIENT_SECRET, clientSecret: configService.get('GITHUB_CLIENT_SECRET'),
callbackURL: process.env.GITHUB_CALLBACK_URL, callbackURL: configService.get('GITHUB_CALLBACK_URL'),
scope: [process.env.GITHUB_SCOPE], scope: [configService.get('GITHUB_SCOPE')],
store: true, store: true,
}); });
} }

View File

@@ -5,18 +5,20 @@ import { UserService } from 'src/user/user.service';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { AuthService } from '../auth.service'; import { AuthService } from '../auth.service';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) { export class GoogleStrategy extends PassportStrategy(Strategy) {
constructor( constructor(
private usersService: UserService, private usersService: UserService,
private authService: AuthService, private authService: AuthService,
private configService: ConfigService,
) { ) {
super({ super({
clientID: process.env.GOOGLE_CLIENT_ID, clientID: configService.get('GOOGLE_CLIENT_ID'),
clientSecret: process.env.GOOGLE_CLIENT_SECRET, clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),
callbackURL: process.env.GOOGLE_CALLBACK_URL, callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
scope: process.env.GOOGLE_SCOPE.split(','), scope: configService.get('GOOGLE_SCOPE').split(','),
passReqToCallback: true, passReqToCallback: true,
store: true, store: true,
}); });

View File

@@ -15,10 +15,14 @@ import {
INVALID_ACCESS_TOKEN, INVALID_ACCESS_TOKEN,
USER_NOT_FOUND, USER_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(private usersService: UserService) { constructor(
private usersService: UserService,
private configService: ConfigService,
) {
super({ super({
jwtFromRequest: ExtractJwt.fromExtractors([ jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => { (request: Request) => {
@@ -29,7 +33,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
return ATCookie; return ATCookie;
}, },
]), ]),
secretOrKey: process.env.JWT_SECRET, secretOrKey: configService.get('JWT_SECRET'),
}); });
} }

View File

@@ -5,19 +5,21 @@ import { AuthService } from '../auth.service';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class MicrosoftStrategy extends PassportStrategy(Strategy) { export class MicrosoftStrategy extends PassportStrategy(Strategy) {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private usersService: UserService, private usersService: UserService,
private configService: ConfigService,
) { ) {
super({ super({
clientID: process.env.MICROSOFT_CLIENT_ID, clientID: configService.get('MICROSOFT_CLIENT_ID'),
clientSecret: process.env.MICROSOFT_CLIENT_SECRET, clientSecret: configService.get('MICROSOFT_CLIENT_SECRET'),
callbackURL: process.env.MICROSOFT_CALLBACK_URL, callbackURL: configService.get('MICROSOFT_CALLBACK_URL'),
scope: [process.env.MICROSOFT_SCOPE], scope: [configService.get('MICROSOFT_SCOPE')],
tenant: process.env.MICROSOFT_TENANT, tenant: configService.get('MICROSOFT_TENANT'),
store: true, store: true,
}); });
} }

View File

@@ -14,10 +14,14 @@ import {
USER_NOT_FOUND, USER_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') { export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
constructor(private usersService: UserService) { constructor(
private usersService: UserService,
private configService: ConfigService,
) {
super({ super({
jwtFromRequest: ExtractJwt.fromExtractors([ jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => { (request: Request) => {
@@ -28,7 +32,7 @@ export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
return RTCookie; return RTCookie;
}, },
]), ]),
secretOrKey: process.env.JWT_SECRET, secretOrKey: configService.get('JWT_SECRET'),
}); });
} }

View File

@@ -7,21 +7,27 @@ import {
MAILER_FROM_ADDRESS_UNDEFINED, MAILER_FROM_ADDRESS_UNDEFINED,
MAILER_SMTP_URL_UNDEFINED, MAILER_SMTP_URL_UNDEFINED,
} from 'src/errors'; } from 'src/errors';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ @Module({
imports: [ imports: [
NestMailerModule.forRoot({ NestMailerModule.forRootAsync({
transport: imports: [ConfigModule],
process.env.MAILER_SMTP_URL ?? throwErr(MAILER_SMTP_URL_UNDEFINED), inject: [ConfigService],
defaults: { useFactory: async (configService: ConfigService) => ({
from: transport:
process.env.MAILER_ADDRESS_FROM ?? configService.get('MAILER_SMTP_URL') ??
throwErr(MAILER_FROM_ADDRESS_UNDEFINED), throwErr(MAILER_SMTP_URL_UNDEFINED),
}, defaults: {
template: { from:
dir: __dirname + '/templates', configService.get('MAILER_ADDRESS_FROM') ??
adapter: new HandlebarsAdapter(), throwErr(MAILER_FROM_ADDRESS_UNDEFINED),
}, },
template: {
dir: __dirname + '/templates',
adapter: new HandlebarsAdapter(),
},
}),
}), }),
], ],
providers: [MailerService], providers: [MailerService],

View File

@@ -20,6 +20,7 @@ import { UserService } from 'src/user/user.service';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { validateEmail } from '../utils'; import { validateEmail } from '../utils';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class TeamInvitationService { export class TeamInvitationService {
@@ -28,8 +29,8 @@ export class TeamInvitationService {
private readonly userService: UserService, private readonly userService: UserService,
private readonly teamService: TeamService, private readonly teamService: TeamService,
private readonly mailerService: MailerService, private readonly mailerService: MailerService,
private readonly pubsub: PubSubService, private readonly pubsub: PubSubService,
private readonly configService: ConfigService,
) {} ) {}
/** /**
@@ -150,7 +151,9 @@ export class TeamInvitationService {
template: 'team-invitation', template: 'team-invitation',
variables: { variables: {
invitee: creator.displayName ?? 'A Hoppscotch User', invitee: creator.displayName ?? 'A Hoppscotch User',
action_url: `${process.env.VITE_BASE_URL}/join-team?id=${dbInvitation.id}`, action_url: `${this.configService.get('VITE_BASE_URL')}/join-team?id=${
dbInvitation.id
}`,
invite_team_name: team.name, invite_team_name: team.name,
}, },
}); });

1135
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff