Skip to content

Commit bc7defc

Browse files
Merge pull request #152 from PretendoNetwork/token-fix
Fix for Invalid Service Token error - Refresh Token Duration same as Access Token
2 parents d28ccbd + 0578bfd commit bc7defc

File tree

12 files changed

+225
-230
lines changed

12 files changed

+225
-230
lines changed

src/database.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { PNIDProfile } from '@/types/services/nnas/pnid-profile';
1313
import { ConnectionData } from '@/types/services/api/connection-data';
1414
import { ConnectionResponse } from '@/types/services/api/connection-response';
1515
import { DiscordConnectionData } from '@/types/services/api/discord-connection-data';
16+
import { SystemType, TokenType } from '@/types/common/token';
1617

1718
const connection_string = config.mongoose.connection_string;
1819
const options = config.mongoose.options;
@@ -112,7 +113,7 @@ export async function getPNIDByNNASAccessToken(token: string): Promise<HydratedP
112113
const unpackedToken = unpackToken(decryptedToken);
113114

114115
// * Return if the system type isn't Wii U (NNAS) and the token type isn't "OAuth Access"
115-
if (unpackedToken.system_type !== 1 || unpackedToken.token_type !== 1) {
116+
if (unpackedToken.system_type !== SystemType.WIIU || unpackedToken.token_type !== TokenType.OAUTH_ACCESS) {
116117
return null;
117118
}
118119

@@ -142,7 +143,7 @@ export async function getPNIDByNNASRefreshToken(token: string): Promise<Hydrated
142143
const unpackedToken = unpackToken(decryptedToken);
143144

144145
// * Return if the system type isn't Wii U (NNAS) and the token type isn't "OAuth Refresh"
145-
if (unpackedToken.system_type !== 1 || unpackedToken.token_type !== 2) {
146+
if (unpackedToken.system_type !== SystemType.WIIU || unpackedToken.token_type !== TokenType.OAUTH_ACCESS) {
146147
return null;
147148
}
148149

@@ -172,7 +173,7 @@ export async function getPNIDByAPIAccessToken(token: string): Promise<HydratedPN
172173
const unpackedToken = unpackToken(decryptedToken);
173174

174175
// * Return if the system type isn't API (REST and gRPC) and the token type isn't "OAuth Access"
175-
if (unpackedToken.system_type !== 3 || unpackedToken.token_type !== 1) {
176+
if (unpackedToken.system_type !== SystemType.API || unpackedToken.token_type !== TokenType.OAUTH_ACCESS) {
176177
return null;
177178
}
178179

@@ -202,7 +203,7 @@ export async function getPNIDByAPIRefreshToken(token: string): Promise<HydratedP
202203
const unpackedToken = unpackToken(decryptedToken);
203204

204205
// * Return if the system type isn't API (REST and gRPC) and the token type isn't "OAuth Refresh"
205-
if (unpackedToken.system_type !== 3 || unpackedToken.token_type !== 2) {
206+
if (unpackedToken.system_type !== SystemType.API || unpackedToken.token_type !== TokenType.OAUTH_REFRESH) {
206207
return null;
207208
}
208209

src/services/api/routes/v1/login.ts

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import express from 'express';
22
import bcrypt from 'bcrypt';
33
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
4-
import { nintendoPasswordHash, generateToken} from '@/util';
5-
import { config } from '@/config-manager';
4+
import { nintendoPasswordHash, generateOAuthTokens} from '@/util';
65
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
6+
import { SystemType } from '@/types/common/token';
77

88
const router = express.Router();
99

@@ -109,38 +109,22 @@ router.post('/', async (request: express.Request, response: express.Response): P
109109
return;
110110
}
111111

112-
const accessTokenOptions = {
113-
system_type: 0x3, // * API
114-
token_type: 0x1, // * OAuth Access
115-
pid: pnid.pid,
116-
access_level: pnid.access_level,
117-
title_id: BigInt(0),
118-
expire_time: BigInt(Date.now() + (3600 * 1000))
119-
};
120-
121-
const refreshTokenOptions = {
122-
system_type: 0x3, // * API
123-
token_type: 0x2, // * OAuth Refresh
124-
pid: pnid.pid,
125-
access_level: pnid.access_level,
126-
title_id: BigInt(0),
127-
expire_time: BigInt(Date.now() + (3600 * 1000))
128-
};
129-
130-
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
131-
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
132-
133-
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
134-
const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
135-
136-
// TODO - Handle null tokens
137-
138-
response.json({
139-
access_token: accessToken,
140-
token_type: 'Bearer',
141-
expires_in: 3600,
142-
refresh_token: newRefreshToken
143-
});
112+
try {
113+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
114+
115+
response.json({
116+
access_token: tokenGeneration.accessToken,
117+
token_type: 'Bearer',
118+
expires_in: tokenGeneration.expiresInSecs.access,
119+
refresh_token: tokenGeneration.refreshToken
120+
});
121+
} catch {
122+
response.status(500).json({
123+
app: 'api',
124+
status: 500,
125+
error: 'Internal server error'
126+
});
127+
}
144128
});
145129

146130
export default router;

src/services/api/routes/v1/register.ts

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import moment from 'moment';
77
import hcaptcha from 'hcaptcha';
88
import Mii from 'mii-js';
99
import { doesPNIDExist, connection as databaseConnection } from '@/database';
10-
import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util';
10+
import { nintendoPasswordHash, sendConfirmationEmail, generateOAuthTokens } from '@/util';
1111
import { LOG_ERROR } from '@/logger';
1212
import { PNID } from '@/models/pnid';
1313
import { NEXAccount } from '@/models/nex-account';
1414
import { config, disabledFeatures } from '@/config-manager';
1515
import { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account';
1616
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
17+
import { SystemType } from '@/types/common/token';
1718

1819
const router = express.Router();
1920

@@ -366,38 +367,22 @@ router.post('/', async (request: express.Request, response: express.Response): P
366367

367368
await sendConfirmationEmail(pnid);
368369

369-
const accessTokenOptions = {
370-
system_type: 0x3, // * API
371-
token_type: 0x1, // * OAuth Access
372-
pid: pnid.pid,
373-
access_level: pnid.access_level,
374-
title_id: BigInt(0),
375-
expire_time: BigInt(Date.now() + (3600 * 1000))
376-
};
377-
378-
const refreshTokenOptions = {
379-
system_type: 0x3, // * API
380-
token_type: 0x2, // * OAuth Refresh
381-
pid: pnid.pid,
382-
access_level: pnid.access_level,
383-
title_id: BigInt(0),
384-
expire_time: BigInt(Date.now() + (3600 * 1000))
385-
};
386-
387-
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
388-
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
389-
390-
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
391-
const refreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
392-
393-
// TODO - Handle null tokens
394-
395-
response.json({
396-
access_token: accessToken,
397-
token_type: 'Bearer',
398-
expires_in: 3600,
399-
refresh_token: refreshToken
400-
});
370+
try {
371+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
372+
373+
response.json({
374+
access_token: tokenGeneration.accessToken,
375+
token_type: 'Bearer',
376+
expires_in: tokenGeneration.expiresInSecs.access,
377+
refresh_token: tokenGeneration.refreshToken
378+
});
379+
} catch {
380+
response.status(500).json({
381+
app: 'api',
382+
status: 500,
383+
error: 'Internal server error'
384+
});
385+
}
401386
});
402387

403388
export default router;

src/services/grpc/api/login.ts

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { Status, ServerError } from 'nice-grpc';
22
import { LoginRequest, LoginResponse, DeepPartial } from '@pretendonetwork/grpc/api/login_rpc';
33
import bcrypt from 'bcrypt';
44
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
5-
import { nintendoPasswordHash, generateToken} from '@/util';
6-
import { config } from '@/config-manager';
5+
import { nintendoPasswordHash, generateOAuthTokens} from '@/util';
76
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
7+
import { SystemType } from '@/types/common/token';
88

99
export async function login(request: LoginRequest): Promise<DeepPartial<LoginResponse>> {
1010
const grantType = request.grantType?.trim();
@@ -16,74 +16,43 @@ export async function login(request: LoginRequest): Promise<DeepPartial<LoginRes
1616
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid grant type');
1717
}
1818

19-
if (grantType === 'password' && !username) {
20-
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing username');
21-
}
22-
23-
if (grantType === 'password' && !password) {
24-
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing password');
25-
}
26-
27-
if (grantType === 'refresh_token' && !refreshToken) {
28-
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token');
29-
}
30-
3119
let pnid: HydratedPNIDDocument | null;
3220

3321
if (grantType === 'password') {
34-
pnid = await getPNIDByUsername(username!); // * We know username will never be null here
22+
if (!username) throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing username');
23+
if (!password) throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing password');
3524

36-
if (!pnid) {
37-
throw new ServerError(Status.INVALID_ARGUMENT, 'User not found');
38-
}
25+
pnid = await getPNIDByUsername(username);
26+
27+
if (!pnid) throw new ServerError(Status.INVALID_ARGUMENT, 'User not found');
3928

40-
const hashedPassword = nintendoPasswordHash(password!, pnid.pid); // * We know password will never be null here
29+
const hashedPassword = nintendoPasswordHash(password, pnid.pid);
4130

4231
if (!bcrypt.compareSync(hashedPassword, pnid.password)) {
4332
throw new ServerError(Status.INVALID_ARGUMENT, 'Password is incorrect');
4433
}
4534
} else {
46-
pnid = await getPNIDByAPIRefreshToken(refreshToken!); // * We know refreshToken will never be null here
35+
if (!refreshToken) throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token');
4736

48-
if (!pnid) {
49-
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token');
50-
}
37+
pnid = await getPNIDByAPIRefreshToken(refreshToken);
38+
39+
if (!pnid) throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token');
5140
}
5241

5342
if (pnid.deleted) {
5443
throw new ServerError(Status.UNAUTHENTICATED, 'Account has been deleted');
5544
}
5645

57-
const accessTokenOptions = {
58-
system_type: 0x3, // * API
59-
token_type: 0x1, // * OAuth Access
60-
pid: pnid.pid,
61-
access_level: pnid.access_level,
62-
title_id: BigInt(0),
63-
expire_time: BigInt(Date.now() + (3600 * 1000))
64-
};
65-
66-
const refreshTokenOptions = {
67-
system_type: 0x3, // * API
68-
token_type: 0x2, // * OAuth Refresh
69-
pid: pnid.pid,
70-
access_level: pnid.access_level,
71-
title_id: BigInt(0),
72-
expire_time: BigInt(Date.now() + (3600 * 1000))
73-
};
74-
75-
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
76-
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
77-
78-
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
79-
const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
80-
81-
// TODO - Handle null tokens
82-
83-
return {
84-
accessToken: accessToken,
85-
tokenType: 'Bearer',
86-
expiresIn: 3600,
87-
refreshToken: newRefreshToken
88-
};
46+
try {
47+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
48+
49+
return {
50+
accessToken: tokenGeneration.accessToken,
51+
tokenType: 'Bearer',
52+
expiresIn: tokenGeneration.expiresInSecs.access,
53+
refreshToken: tokenGeneration.refreshToken
54+
};
55+
} catch {
56+
throw new ServerError(Status.INTERNAL, 'Could not generate OAuth tokens');
57+
}
8958
}

src/services/grpc/api/register.ts

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import moment from 'moment';
88
import hcaptcha from 'hcaptcha';
99
import Mii from 'mii-js';
1010
import { doesPNIDExist, connection as databaseConnection } from '@/database';
11-
import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util';
11+
import { nintendoPasswordHash, sendConfirmationEmail, generateOAuthTokens } from '@/util';
1212
import { LOG_ERROR } from '@/logger';
1313
import { PNID } from '@/models/pnid';
1414
import { NEXAccount } from '@/models/nex-account';
1515
import { config, disabledFeatures } from '@/config-manager';
1616
import type { HydratedNEXAccountDocument } from '@/types/mongoose/nex-account';
1717
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
18+
import { SystemType } from '@/types/common/token';
1819

1920
const PNID_VALID_CHARACTERS_REGEX = /^[\w\-.]*$/;
2021
const PNID_PUNCTUATION_START_REGEX = /^[_\-.]/;
@@ -229,36 +230,16 @@ export async function register(request: RegisterRequest): Promise<DeepPartial<Lo
229230

230231
await sendConfirmationEmail(pnid);
231232

232-
const accessTokenOptions = {
233-
system_type: 0x3, // * API
234-
token_type: 0x1, // * OAuth Access
235-
pid: pnid.pid,
236-
access_level: pnid.access_level,
237-
title_id: BigInt(0),
238-
expire_time: BigInt(Date.now() + (3600 * 1000))
239-
};
240-
241-
const refreshTokenOptions = {
242-
system_type: 0x3, // * API
243-
token_type: 0x2, // * OAuth Refresh
244-
pid: pnid.pid,
245-
access_level: pnid.access_level,
246-
title_id: BigInt(0),
247-
expire_time: BigInt(Date.now() + (3600 * 1000))
248-
};
249-
250-
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
251-
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
252-
253-
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
254-
const refreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
255-
256-
// TODO - Handle null tokens
257-
258-
return {
259-
accessToken: accessToken,
260-
tokenType: 'Bearer',
261-
expiresIn: 3600,
262-
refreshToken: refreshToken
263-
};
233+
try {
234+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
235+
236+
return {
237+
accessToken: tokenGeneration.accessToken,
238+
tokenType: 'Bearer',
239+
expiresIn: tokenGeneration.expiresInSecs.access,
240+
refreshToken: tokenGeneration.refreshToken
241+
};
242+
} catch {
243+
throw new ServerError(Status.INTERNAL, 'Could not generate OAuth tokens');
244+
}
264245
}

0 commit comments

Comments
 (0)