feat: expose local auth endpoints
This commit is contained in:
parent
0ec0ae442a
commit
60cf156230
2 changed files with 179 additions and 0 deletions
144
packages/hoppscotch-backend/src/auth/auth.controller.spec.ts
Normal file
144
packages/hoppscotch-backend/src/auth/auth.controller.spec.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import { Response } from 'express';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LocalAuthService } from './local-auth.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
|
||||
const mockAuthService = mockDeep<AuthService>();
|
||||
const mockConfigService = mockDeep<ConfigService>();
|
||||
const mockLocalAuthService = mockDeep<LocalAuthService>();
|
||||
|
||||
const authController = new AuthController(
|
||||
mockAuthService,
|
||||
mockConfigService,
|
||||
mockLocalAuthService,
|
||||
);
|
||||
|
||||
const currentTime = new Date();
|
||||
const adminUser: AuthUser = {
|
||||
uid: 'admin-1',
|
||||
username: 'admin',
|
||||
email: null,
|
||||
displayName: 'Admin',
|
||||
photoURL: null,
|
||||
isAdmin: true,
|
||||
refreshToken: null,
|
||||
lastLoggedOn: currentTime,
|
||||
lastActiveOn: currentTime,
|
||||
createdOn: currentTime,
|
||||
currentGQLSession: null,
|
||||
currentRESTSession: null,
|
||||
};
|
||||
|
||||
function createMockResponse() {
|
||||
const res = {
|
||||
cookie: jest.fn(),
|
||||
status: jest.fn().mockReturnThis(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
return res as unknown as Response & typeof res;
|
||||
}
|
||||
|
||||
describe('AuthController local auth', () => {
|
||||
beforeEach(() => {
|
||||
mockReset(mockAuthService);
|
||||
mockReset(mockConfigService);
|
||||
mockReset(mockLocalAuthService);
|
||||
mockConfigService.get.mockImplementation((key: string) => {
|
||||
if (key === 'INFRA.ACCESS_TOKEN_VALIDITY') return '86400000';
|
||||
if (key === 'INFRA.REFRESH_TOKEN_VALIDITY') return '604800000';
|
||||
if (key === 'INFRA.ALLOW_SECURE_COOKIES') return 'false';
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
it('sets auth cookies after local signin succeeds', async () => {
|
||||
const res = createMockResponse();
|
||||
mockLocalAuthService.signIn.mockResolvedValue(
|
||||
E.right({
|
||||
access_token: 'access-token',
|
||||
refresh_token: 'refresh-token',
|
||||
}),
|
||||
);
|
||||
|
||||
await authController.signInLocal(
|
||||
{
|
||||
username: 'admin',
|
||||
password: 'strong-password',
|
||||
},
|
||||
res,
|
||||
);
|
||||
|
||||
expect(mockLocalAuthService.signIn).toHaveBeenCalledWith({
|
||||
username: 'admin',
|
||||
password: 'strong-password',
|
||||
});
|
||||
expect(res.cookie).toHaveBeenCalledTimes(2);
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
});
|
||||
|
||||
it('sets auth cookies after local setup admin succeeds', async () => {
|
||||
const res = createMockResponse();
|
||||
mockLocalAuthService.setupFirstAdmin.mockResolvedValue(
|
||||
E.right({
|
||||
access_token: 'access-token',
|
||||
refresh_token: 'refresh-token',
|
||||
}),
|
||||
);
|
||||
|
||||
await authController.setupLocalAdmin(
|
||||
{
|
||||
username: 'admin',
|
||||
password: 'strong-password',
|
||||
},
|
||||
res,
|
||||
);
|
||||
|
||||
expect(mockLocalAuthService.setupFirstAdmin).toHaveBeenCalledWith({
|
||||
username: 'admin',
|
||||
password: 'strong-password',
|
||||
});
|
||||
expect(res.cookie).toHaveBeenCalledTimes(2);
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
});
|
||||
|
||||
it('delegates local user creation to the local auth service', async () => {
|
||||
mockLocalAuthService.createLocalUser.mockResolvedValue(
|
||||
E.right({
|
||||
uid: 'user-1',
|
||||
username: 'dwight',
|
||||
displayName: 'Dwight Schrute',
|
||||
email: null,
|
||||
photoURL: null,
|
||||
isAdmin: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await authController.createLocalUser(adminUser, {
|
||||
username: 'dwight',
|
||||
password: 'strong-password',
|
||||
displayName: 'Dwight Schrute',
|
||||
});
|
||||
|
||||
expect(mockLocalAuthService.createLocalUser).toHaveBeenCalledWith(
|
||||
{
|
||||
username: 'dwight',
|
||||
password: 'strong-password',
|
||||
displayName: 'Dwight Schrute',
|
||||
},
|
||||
adminUser,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
uid: 'user-1',
|
||||
username: 'dwight',
|
||||
displayName: 'Dwight Schrute',
|
||||
email: null,
|
||||
photoURL: null,
|
||||
isAdmin: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -30,6 +30,12 @@ import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
import { throwHTTPErr } from 'src/utils';
|
||||
import { UserLastLoginInterceptor } from 'src/interceptors/user-last-login.interceptor';
|
||||
import {
|
||||
CreateLocalUserDto,
|
||||
LocalSetupAdminDto,
|
||||
LocalSignInDto,
|
||||
} from './dto/local-auth.dto';
|
||||
import { LocalAuthService } from './local-auth.service';
|
||||
|
||||
@UseGuards(ThrottlerBehindProxyGuard)
|
||||
@Controller({ path: 'auth', version: '1' })
|
||||
|
|
@ -37,6 +43,7 @@ export class AuthController {
|
|||
constructor(
|
||||
private authService: AuthService,
|
||||
private configService: ConfigService,
|
||||
private localAuthService: LocalAuthService,
|
||||
) {}
|
||||
|
||||
@Get('providers')
|
||||
|
|
@ -80,6 +87,34 @@ export class AuthController {
|
|||
authCookieHandler(res, authTokens.right, false, null, this.configService);
|
||||
}
|
||||
|
||||
@Post('local/signin')
|
||||
async signInLocal(@Body() data: LocalSignInDto, @Res() res: Response) {
|
||||
const authTokens = await this.localAuthService.signIn(data);
|
||||
if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left);
|
||||
authCookieHandler(res, authTokens.right, false, null, this.configService);
|
||||
}
|
||||
|
||||
@Post('local/setup-admin')
|
||||
async setupLocalAdmin(
|
||||
@Body() data: LocalSetupAdminDto,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const authTokens = await this.localAuthService.setupFirstAdmin(data);
|
||||
if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left);
|
||||
authCookieHandler(res, authTokens.right, false, null, this.configService);
|
||||
}
|
||||
|
||||
@Post('local/users')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async createLocalUser(
|
||||
@GqlUser() user: AuthUser,
|
||||
@Body() data: CreateLocalUserDto,
|
||||
) {
|
||||
const createdUser = await this.localAuthService.createLocalUser(data, user);
|
||||
if (E.isLeft(createdUser)) throwHTTPErr(createdUser.left);
|
||||
return createdUser.right;
|
||||
}
|
||||
|
||||
/**
|
||||
** Route to refresh auth tokens with Refresh Token Rotation
|
||||
* @see https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation
|
||||
|
|
|
|||
Loading…
Reference in a new issue