Skip to content

Commit a36f49c

Browse files
Fix protocol and hostname resolution (#297)
Co-authored-by: Grzegorz Krajniak <[email protected]>
1 parent a857236 commit a36f49c

File tree

3 files changed

+92
-18
lines changed

3 files changed

+92
-18
lines changed

src/auth/auth-token.service.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Inject, Injectable } from '@nestjs/common';
1010
import { AxiosError } from 'axios';
1111
import type { Request, Response } from 'express';
1212
import { catchError, firstValueFrom } from 'rxjs';
13+
import { getRedirectUri } from './redirect-uri.js';
1314

1415
export interface AuthTokenData {
1516
access_token: string;
@@ -43,7 +44,7 @@ export class AuthTokenService {
4344
code: string,
4445
): Promise<AuthTokenData> {
4546
const authConfig = await this.authConfigService.getAuthConfig(request);
46-
const redirectUri = this.getRedirectUri(request);
47+
const redirectUri = getRedirectUri(request, this.envService.getEnv());
4748

4849
const body = new URLSearchParams({
4950
client_id: authConfig.clientId,
@@ -121,21 +122,4 @@ export class AuthTokenService {
121122
`Unexpected response code from auth token server: ${tokenFetchResult.status}, ${tokenFetchResult.statusText}`,
122123
);
123124
}
124-
125-
/**
126-
* Redirection URL is calculated based on the ENVIRONMENT system variable.
127-
* When running locally after executing token retrieval the call will be redirected to localhost and port set in FRONTEND_PORT system variable,
128-
* otherwise to the host of the initiating request
129-
*/
130-
private getRedirectUri(request: Request) {
131-
let redirectionUrl: string;
132-
const env = this.envService.getEnv();
133-
const isStandardOrEmptyPort =
134-
env.frontendPort === '80' ||
135-
env.frontendPort === '443' ||
136-
!env.frontendPort;
137-
const port = isStandardOrEmptyPort ? '' : ':' + env.frontendPort;
138-
redirectionUrl = `${request.protocol}://${request.hostname}${port}`;
139-
return `${redirectionUrl}/callback?storageType=none`;
140-
}
141125
}

src/auth/redirect-uri.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { getRedirectUri } from './redirect-uri.js';
2+
import type { Request } from 'express';
3+
import { EnvVariables } from '../env/env.service.js';
4+
5+
describe('getRedirectUri', () => {
6+
const baseEnv: EnvVariables = {
7+
frontendPort: '3000',
8+
} as any;
9+
10+
const makeReq = (headers: Record<string, any> = {}, hostname = 'example.com', protocol = 'http'): Request =>
11+
({
12+
headers,
13+
hostname,
14+
protocol,
15+
} as Request);
16+
17+
it('uses x-forwarded-proto and x-forwarded-host when present', () => {
18+
const req = makeReq({
19+
'x-forwarded-proto': 'https',
20+
'x-forwarded-host': 'forwarded.com',
21+
});
22+
const result = getRedirectUri(req, baseEnv);
23+
expect(result).toBe('https://forwarded.com:3000/callback?storageType=none');
24+
});
25+
26+
it('uses first value when x-forwarded-proto and host are arrays', () => {
27+
const req = makeReq({
28+
'x-forwarded-proto': ['https', 'http'],
29+
'x-forwarded-host': ['multi.com:8080', 'other.com'],
30+
});
31+
const result = getRedirectUri(req, baseEnv);
32+
expect(result).toBe('https://multi.com:3000/callback?storageType=none');
33+
});
34+
35+
it('falls back to request protocol and hostname if headers missing', () => {
36+
const req = makeReq({}, 'local.dev', 'http');
37+
const result = getRedirectUri(req, baseEnv);
38+
expect(result).toBe('http://local.dev:3000/callback?storageType=none');
39+
});
40+
41+
it('omits port if standard (80 or 443)', () => {
42+
const env80 = { frontendPort: '80' } as EnvVariables;
43+
const env443 = { frontendPort: '443' } as EnvVariables;
44+
const req = makeReq({ 'x-forwarded-proto': 'https', 'x-forwarded-host': 'secure.com' });
45+
expect(getRedirectUri(req, env80)).toBe('https://secure.com/callback?storageType=none');
46+
expect(getRedirectUri(req, env443)).toBe('https://secure.com/callback?storageType=none');
47+
});
48+
49+
it('omits port if env.frontendPort is empty', () => {
50+
const envEmpty = { frontendPort: '' } as EnvVariables;
51+
const req = makeReq({ 'x-forwarded-proto': 'https', 'x-forwarded-host': 'noport.com' });
52+
const result = getRedirectUri(req, envEmpty);
53+
expect(result).toBe('https://noport.com/callback?storageType=none');
54+
});
55+
56+
it('extracts hostname correctly when x-forwarded-host includes port', () => {
57+
const req = makeReq({ 'x-forwarded-proto': 'https', 'x-forwarded-host': 'withport.com:8080' });
58+
const result = getRedirectUri(req, baseEnv);
59+
expect(result).toBe('https://withport.com:3000/callback?storageType=none');
60+
});
61+
});

src/auth/redirect-uri.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Request } from 'express';
2+
import {EnvVariables} from "../env/env.service.js";
3+
4+
/**
5+
* Redirection URL is calculated based on the incoming request
6+
*/
7+
export const getRedirectUri = (request: Request, env: EnvVariables)=> {
8+
const isStandardOrEmptyPort =
9+
env.frontendPort === '80' ||
10+
env.frontendPort === '443' ||
11+
!env.frontendPort;
12+
const port = isStandardOrEmptyPort ? '' : ':' + env.frontendPort;
13+
14+
const forwardedProto = request.headers['x-forwarded-proto'];
15+
const forwardedProtoValue = Array.isArray(forwardedProto)
16+
? forwardedProto[0]
17+
: forwardedProto;
18+
const protocol = forwardedProtoValue || request.protocol;
19+
20+
const forwardedHost = request.headers['x-forwarded-host'];
21+
const forwardedHostValue = Array.isArray(forwardedHost)
22+
? forwardedHost[0]
23+
: forwardedHost
24+
const forwardedHostname = forwardedHostValue?.split(':')[0];
25+
const host = forwardedHostname || request.hostname;
26+
27+
const redirectionUrl = `${protocol}://${host}${port}`;
28+
return `${redirectionUrl}/callback?storageType=none`;
29+
}

0 commit comments

Comments
 (0)