Skip to content

Commit 0578bfd

Browse files
chore: move back to number types for token, with const enum
1 parent 1197f71 commit 0578bfd

File tree

10 files changed

+55
-85
lines changed

10 files changed

+55
-85
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 !== 'WIIU' || unpackedToken.token_type !== 'OAUTH_ACCESS') {
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 !== 'WIIU' || unpackedToken.token_type !== 'OAUTH_REFRESH') {
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 !== 'API' || unpackedToken.token_type !== 'OAUTH_ACCESS') {
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 !== 'API' || unpackedToken.token_type !== 'OAUTH_REFRESH') {
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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import bcrypt from 'bcrypt';
33
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
44
import { nintendoPasswordHash, generateOAuthTokens} from '@/util';
55
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
6+
import { SystemType } from '@/types/common/token';
67

78
const router = express.Router();
89

@@ -109,7 +110,7 @@ router.post('/', async (request: express.Request, response: express.Response): P
109110
}
110111

111112
try {
112-
const tokenGeneration = generateOAuthTokens('API', pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
113+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
113114

114115
response.json({
115116
access_token: tokenGeneration.accessToken,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ 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

@@ -367,7 +368,7 @@ router.post('/', async (request: express.Request, response: express.Response): P
367368
await sendConfirmationEmail(pnid);
368369

369370
try {
370-
const tokenGeneration = generateOAuthTokens('API', pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
371+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
371372

372373
response.json({
373374
access_token: tokenGeneration.accessToken,

src/services/grpc/api/login.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import bcrypt from 'bcrypt';
44
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
55
import { nintendoPasswordHash, generateOAuthTokens} from '@/util';
66
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
7+
import { SystemType } from '@/types/common/token';
78

89
export async function login(request: LoginRequest): Promise<DeepPartial<LoginResponse>> {
910
const grantType = request.grantType?.trim();
@@ -43,7 +44,7 @@ export async function login(request: LoginRequest): Promise<DeepPartial<LoginRes
4344
}
4445

4546
try {
46-
const tokenGeneration = generateOAuthTokens('API', pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
47+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
4748

4849
return {
4950
accessToken: tokenGeneration.accessToken,

src/services/grpc/api/register.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ 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 = /^[_\-.]/;
@@ -230,7 +231,7 @@ export async function register(request: RegisterRequest): Promise<DeepPartial<Lo
230231
await sendConfirmationEmail(pnid);
231232

232233
try {
233-
const tokenGeneration = generateOAuthTokens('API', pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
234+
const tokenGeneration = generateOAuthTokens(SystemType.API, pnid, { refreshExpiresIn: 14 * 24 * 60 * 60 }); // * 14 days
234235

235236
return {
236237
accessToken: tokenGeneration.accessToken,

src/services/nasc/routes/ac.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { nintendoBase64Encode, nintendoBase64Decode, nascDateTime, nascError, ge
33
import { getServerByTitleID } from '@/database';
44
import { NASCRequestParams } from '@/types/services/nasc/request-params';
55
import { HydratedServerDocument } from '@/types/mongoose/server';
6-
import { TokenOptions } from '@/types/common/token';
6+
import { SystemType, TokenOptions, TokenType } from '@/types/common/token';
77

88
const router = express.Router();
99

@@ -66,8 +66,8 @@ router.post('/', async (request: express.Request, response: express.Response): P
6666

6767
async function processLoginRequest(server: HydratedServerDocument, pid: number, titleID: string): Promise<URLSearchParams> {
6868
const tokenOptions: TokenOptions = {
69-
system_type: '3DS',
70-
token_type: 'NEX',
69+
system_type: SystemType['3DS'],
70+
token_type: TokenType.NEX,
7171
pid: pid,
7272
access_level: 0,
7373
title_id: BigInt(parseInt(titleID, 16)),
@@ -90,8 +90,8 @@ async function processLoginRequest(server: HydratedServerDocument, pid: number,
9090

9191
async function processServiceTokenRequest(server: HydratedServerDocument, pid: number, titleID: string): Promise<URLSearchParams> {
9292
const tokenOptions: TokenOptions = {
93-
system_type: '3DS',
94-
token_type: 'SERVICE',
93+
system_type: SystemType['3DS'],
94+
token_type: TokenType.SERVICE,
9595
pid: pid,
9696
access_level: 0,
9797
title_id: BigInt(parseInt(titleID, 16)),

src/services/nnas/routes/oauth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import consoleStatusVerificationMiddleware from '@/middleware/console-status-ver
66
import { getPNIDByNNASRefreshToken, getPNIDByUsername } from '@/database';
77
import { generateOAuthTokens } from '@/util';
88
import { Device } from '@/models/device';
9+
import { SystemType } from '@/types/common/token';
910

1011
const router = express.Router();
1112

@@ -153,7 +154,7 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus
153154
}
154155

155156
try {
156-
const tokenGeneration = generateOAuthTokens('WIIU', pnid);
157+
const tokenGeneration = generateOAuthTokens(SystemType.WIIU, pnid);
157158

158159
response.send(xmlbuilder.create({
159160
OAuth20: {

src/services/nnas/routes/provider.ts

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import xmlbuilder from 'xmlbuilder';
33
import { getServerByClientID, getServerByGameServerID } from '@/database';
44
import { generateToken, getValueFromHeaders, getValueFromQueryString } from '@/util';
55
import { NEXAccount } from '@/models/nex-account';
6-
import { TokenOptions } from '@/types/common/token';
7-
import { serverDeviceToSystemType } from '@/types/common/token';
6+
import { SystemType, TokenOptions, TokenType } from '@/types/common/token';
87

98
const router = express.Router();
109

@@ -91,23 +90,16 @@ router.get('/service_token/@me', async (request: express.Request, response: expr
9190
return;
9291
}
9392

94-
const systemType = serverDeviceToSystemType[server.device];
95-
if (!systemType) {
96-
response.send(xmlbuilder.create({
97-
errors: {
98-
error: {
99-
code: '1021',
100-
message: 'The requested game server was not found'
101-
}
102-
}
103-
}).end());
104-
105-
return;
93+
if (!(server.device in Object.values(SystemType))) {
94+
throw new Error('Invalid system type');
10695
}
10796

97+
// * Asserted safely because of the check above
98+
const systemType = server.device as SystemType;
99+
108100
const tokenOptions: TokenOptions = {
109-
system_type: systemType,
110-
token_type: 'SERVICE',
101+
system_type: systemType as SystemType,
102+
token_type: TokenType.SERVICE,
111103
pid: pnid.pid,
112104
access_level: pnid.access_level,
113105
title_id: BigInt(parseInt(titleID, 16)),
@@ -228,23 +220,16 @@ router.get('/nex_token/@me', async (request: express.Request, response: express.
228220
return;
229221
}
230222

231-
const systemType = serverDeviceToSystemType[server.device];
232-
if (!systemType) {
233-
response.send(xmlbuilder.create({
234-
errors: {
235-
error: {
236-
code: '1021',
237-
message: 'The requested game server was not found'
238-
}
239-
}
240-
}).end());
241-
242-
return;
223+
if (!(server.device in Object.values(SystemType))) {
224+
throw new Error('Invalid system type');
243225
}
244226

227+
// * Asserted safely because of the check above
228+
const systemType = server.device as SystemType;
229+
245230
const tokenOptions: TokenOptions = {
246-
system_type: systemType,
247-
token_type: 'NEX',
231+
system_type: systemType,
232+
token_type: TokenType.NEX,
248233
pid: pnid.pid,
249234
access_level: pnid.access_level,
250235
title_id: BigInt(parseInt(titleID, 16)),

src/types/common/token.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,18 @@
1-
export const TokenTypes = {
1+
export const TokenType = {
22
OAUTH_ACCESS: 1,
33
OAUTH_REFRESH: 2,
44
NEX: 3,
55
SERVICE: 4,
66
PASSWORD_RESET: 5
77
} as const;
8-
export type TokenType = keyof typeof TokenTypes;
8+
export type TokenType = typeof TokenType[keyof typeof TokenType];
99

10-
export function getTokenTypeFromValue(type: number): TokenType | undefined {
11-
const keys = Object.keys(TokenTypes) as TokenType[];
12-
return keys.find((key) => TokenTypes[key] === type);
13-
}
14-
15-
export const SystemTypes = {
10+
export const SystemType = {
1611
'WIIU': 1,
1712
'3DS': 2,
1813
'API': 3
1914
} as const;
20-
export type SystemType = keyof typeof SystemTypes;
21-
22-
export const serverDeviceToSystemType: Record<number, SystemType> = {
23-
1: 'WIIU',
24-
2: '3DS'
25-
};
26-
27-
export function getSystemTypeFromValue(type: number): SystemType | undefined {
28-
const keys = Object.keys(SystemTypes) as SystemType[];
29-
return keys.find((key) => SystemTypes[key] === type);
30-
}
15+
export type SystemType = typeof SystemType[keyof typeof SystemType];
3116

3217
export interface Token {
3318
system_type: SystemType;

src/util.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import crc32 from 'buffer-crc32';
1010
import crc from 'crc';
1111
import { sendMail } from '@/mailer';
1212
import { config, disabledFeatures } from '@/config-manager';
13-
import { getSystemTypeFromValue, getTokenTypeFromValue, OAuthTokenGenerationResponse, OAuthTokenOptions, SystemType, SystemTypes, Token, TokenOptions, TokenTypes } from '@/types/common/token';
13+
import { OAuthTokenGenerationResponse, OAuthTokenOptions, SystemType, Token, TokenOptions, TokenType } from '@/types/common/token';
1414
import { HydratedPNIDDocument, IPNID, IPNIDMethods } from '@/types/mongoose/pnid';
1515
import { SafeQs } from '@/types/common/safe-qs';
1616

@@ -58,15 +58,15 @@ export function generateOAuthTokens(systemType: SystemType, pnid: HydratedPNIDDo
5858

5959
const accessTokenOptions: TokenOptions = {
6060
system_type: systemType,
61-
token_type: 'OAUTH_ACCESS',
61+
token_type: TokenType.OAUTH_ACCESS,
6262
pid: pnid.pid,
6363
access_level: pnid.access_level,
6464
expire_time: BigInt(Date.now() + (accessTokenExpiresInSecs * 1000))
6565
};
6666

6767
const refreshTokenOptions: TokenOptions = {
6868
system_type: systemType,
69-
token_type: 'OAUTH_REFRESH',
69+
token_type: TokenType.OAUTH_REFRESH,
7070
pid: pnid.pid,
7171
access_level: pnid.access_level,
7272
expire_time: BigInt(Date.now() + (refreshTokenExpiresInSecs * 1000))
@@ -92,15 +92,12 @@ export function generateOAuthTokens(systemType: SystemType, pnid: HydratedPNIDDo
9292
export function generateToken(key: string, options: TokenOptions): Buffer | null {
9393
let dataBuffer = Buffer.alloc(1 + 1 + 4 + 8);
9494

95-
const systemType = SystemTypes[options.system_type];
96-
const tokenType = TokenTypes[options.token_type];
97-
98-
dataBuffer.writeUInt8(systemType, 0x0);
99-
dataBuffer.writeUInt8(tokenType, 0x1);
95+
dataBuffer.writeUInt8(options.system_type, 0x0);
96+
dataBuffer.writeUInt8(options.token_type, 0x1);
10097
dataBuffer.writeUInt32LE(options.pid, 0x2);
10198
dataBuffer.writeBigUInt64LE(options.expire_time, 0x6);
10299

103-
if ((options.token_type !== 'OAUTH_ACCESS' && options.token_type !== 'OAUTH_REFRESH') || options.system_type === 'API') {
100+
if ((options.token_type !== TokenType.OAUTH_ACCESS && options.token_type !== TokenType.OAUTH_REFRESH) || options.system_type === SystemType.API) {
104101
// * Access and refresh tokens have smaller bodies due to size constraints
105102
// * The API does not have this restraint, however
106103
if (options.title_id === undefined || options.access_level === undefined) {
@@ -126,7 +123,7 @@ export function generateToken(key: string, options: TokenOptions): Buffer | null
126123

127124
let final = encrypted;
128125

129-
if ((options.token_type !== 'OAUTH_ACCESS' && options.token_type !== 'OAUTH_REFRESH') || options.system_type === 'API') {
126+
if ((options.token_type !== TokenType.OAUTH_ACCESS && options.token_type !== TokenType.OAUTH_REFRESH) || options.system_type === SystemType.API) {
130127
// * Access and refresh tokens don't get a checksum due to size constraints
131128
const checksum = crc32(dataBuffer);
132129

@@ -171,23 +168,20 @@ export function decryptToken(token: Buffer, key?: string): Buffer {
171168
}
172169

173170
export function unpackToken(token: Buffer): Token {
174-
const systemTypeNum = token.readUInt8(0x0);
175-
const tokenTypeNum = token.readUInt8(0x1);
176-
177-
const systemType = getSystemTypeFromValue(systemTypeNum);
178-
const tokenType = getTokenTypeFromValue(tokenTypeNum);
171+
const systemType = token.readUInt8(0x0);
172+
const tokenType = token.readUInt8(0x1);
179173

180-
if (!systemType) throw new Error('Invalid system type');
181-
if (!tokenType) throw new Error('Invalid token type');
174+
if (!(systemType in Object.values(SystemType))) throw new Error('Invalid system type');
175+
if (!(tokenType in Object.values(TokenType))) throw new Error('Invalid token type');
182176

183177
const unpacked: Token = {
184-
system_type: systemType,
185-
token_type: tokenType,
178+
system_type: systemType as SystemType,
179+
token_type: tokenType as TokenType,
186180
pid: token.readUInt32LE(0x2),
187181
expire_time: token.readBigUInt64LE(0x6)
188182
};
189183

190-
if (unpacked.token_type !== 'OAUTH_ACCESS' && unpacked.token_type !== 'OAUTH_REFRESH') {
184+
if (unpacked.token_type !== TokenType.OAUTH_ACCESS && unpacked.token_type !== TokenType.OAUTH_REFRESH) {
191185
unpacked.title_id = token.readBigUInt64LE(0xE);
192186
unpacked.access_level = token.readInt8(0x16);
193187
}
@@ -274,8 +268,8 @@ export async function sendEmailConfirmedEmail(pnid: mongoose.HydratedDocument<IP
274268

275269
export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument<IPNID, IPNIDMethods>): Promise<void> {
276270
const tokenOptions: TokenOptions = {
277-
system_type: 'API',
278-
token_type: 'PASSWORD_RESET',
271+
system_type: SystemType.API,
272+
token_type: TokenType.PASSWORD_RESET,
279273
pid: pnid.pid,
280274
access_level: pnid.access_level,
281275
title_id: BigInt(0),

0 commit comments

Comments
 (0)