mirror of
https://github.com/PretendoNetwork/account.git
synced 2026-04-25 07:22:47 -05:00
fix: make refresh token expire longer than access token, and centralise oauth generation
This commit is contained in:
parent
d28ccbdf95
commit
1ecb7ad473
|
|
@ -1,8 +1,7 @@
|
|||
import express from 'express';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
|
||||
import { nintendoPasswordHash, generateToken} from '@/util';
|
||||
import { config } from '@/config-manager';
|
||||
import { nintendoPasswordHash, generateOAuthTokens} from '@/util';
|
||||
import { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
|
||||
const router = express.Router();
|
||||
|
|
@ -109,38 +108,23 @@ router.post('/', async (request: express.Request, response: express.Response): P
|
|||
return;
|
||||
}
|
||||
|
||||
const accessTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x1, // * OAuth Access
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
try {
|
||||
const systemType = 0x3; // * API
|
||||
const { accessToken, refreshToken, accessTokenExpiresInSecs } = generateOAuthTokens(systemType, pnid);
|
||||
|
||||
const refreshTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x2, // * OAuth Refresh
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
|
||||
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
|
||||
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
|
||||
|
||||
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
|
||||
const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
|
||||
|
||||
// TODO - Handle null tokens
|
||||
|
||||
response.json({
|
||||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
expires_in: 3600,
|
||||
refresh_token: newRefreshToken
|
||||
});
|
||||
response.json({
|
||||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
expires_in: accessTokenExpiresInSecs,
|
||||
refresh_token: refreshToken
|
||||
});
|
||||
} catch {
|
||||
response.status(500).json({
|
||||
app: 'api',
|
||||
status: 500,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -7,7 +7,7 @@ import moment from 'moment';
|
|||
import hcaptcha from 'hcaptcha';
|
||||
import Mii from 'mii-js';
|
||||
import { doesPNIDExist, connection as databaseConnection } from '@/database';
|
||||
import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util';
|
||||
import { nintendoPasswordHash, sendConfirmationEmail, generateOAuthTokens } from '@/util';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
import { PNID } from '@/models/pnid';
|
||||
import { NEXAccount } from '@/models/nex-account';
|
||||
|
|
@ -366,38 +366,23 @@ router.post('/', async (request: express.Request, response: express.Response): P
|
|||
|
||||
await sendConfirmationEmail(pnid);
|
||||
|
||||
const accessTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x1, // * OAuth Access
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
try {
|
||||
const systemType = 0x3 // * API
|
||||
const { accessToken, refreshToken, accessTokenExpiresInSecs } = generateOAuthTokens(systemType, pnid);
|
||||
|
||||
const refreshTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x2, // * OAuth Refresh
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
|
||||
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
|
||||
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
|
||||
|
||||
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
|
||||
const refreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
|
||||
|
||||
// TODO - Handle null tokens
|
||||
|
||||
response.json({
|
||||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
expires_in: 3600,
|
||||
refresh_token: refreshToken
|
||||
});
|
||||
response.json({
|
||||
access_token: accessToken,
|
||||
token_type: 'Bearer',
|
||||
expires_in: accessTokenExpiresInSecs,
|
||||
refresh_token: refreshToken
|
||||
});
|
||||
} catch {
|
||||
response.status(500).json({
|
||||
app: 'api',
|
||||
status: 500,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -2,8 +2,7 @@ import { Status, ServerError } from 'nice-grpc';
|
|||
import { LoginRequest, LoginResponse, DeepPartial } from '@pretendonetwork/grpc/api/login_rpc';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
|
||||
import { nintendoPasswordHash, generateToken} from '@/util';
|
||||
import { config } from '@/config-manager';
|
||||
import { nintendoPasswordHash, generateOAuthTokens} from '@/util';
|
||||
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
|
||||
export async function login(request: LoginRequest): Promise<DeepPartial<LoginResponse>> {
|
||||
|
|
@ -54,36 +53,17 @@ export async function login(request: LoginRequest): Promise<DeepPartial<LoginRes
|
|||
throw new ServerError(Status.UNAUTHENTICATED, 'Account has been deleted');
|
||||
}
|
||||
|
||||
const accessTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x1, // * OAuth Access
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
try {
|
||||
const systemType = 0x3; // * API
|
||||
const { accessToken, refreshToken, accessTokenExpiresInSecs } = generateOAuthTokens(systemType, pnid);
|
||||
|
||||
const refreshTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x2, // * OAuth Refresh
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
|
||||
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
|
||||
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
|
||||
|
||||
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
|
||||
const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
|
||||
|
||||
// TODO - Handle null tokens
|
||||
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
tokenType: 'Bearer',
|
||||
expiresIn: 3600,
|
||||
refreshToken: newRefreshToken
|
||||
};
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
tokenType: 'Bearer',
|
||||
expiresIn: accessTokenExpiresInSecs,
|
||||
refreshToken: refreshToken
|
||||
};
|
||||
} catch {
|
||||
throw new ServerError(Status.INTERNAL, 'Could not generate OAuth tokens');
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import moment from 'moment';
|
|||
import hcaptcha from 'hcaptcha';
|
||||
import Mii from 'mii-js';
|
||||
import { doesPNIDExist, connection as databaseConnection } from '@/database';
|
||||
import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util';
|
||||
import { nintendoPasswordHash, sendConfirmationEmail, generateOAuthTokens } from '@/util';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
import { PNID } from '@/models/pnid';
|
||||
import { NEXAccount } from '@/models/nex-account';
|
||||
|
|
@ -229,36 +229,17 @@ export async function register(request: RegisterRequest): Promise<DeepPartial<Lo
|
|||
|
||||
await sendConfirmationEmail(pnid);
|
||||
|
||||
const accessTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x1, // * OAuth Access
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
try {
|
||||
const systemType = 0x3 // * API
|
||||
const { accessToken, refreshToken, accessTokenExpiresInSecs } = generateOAuthTokens(systemType, pnid);
|
||||
|
||||
const refreshTokenOptions = {
|
||||
system_type: 0x3, // * API
|
||||
token_type: 0x2, // * OAuth Refresh
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
title_id: BigInt(0),
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
|
||||
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
|
||||
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
|
||||
|
||||
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
|
||||
const refreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
|
||||
|
||||
// TODO - Handle null tokens
|
||||
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
tokenType: 'Bearer',
|
||||
expiresIn: 3600,
|
||||
refreshToken: refreshToken
|
||||
};
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
tokenType: 'Bearer',
|
||||
expiresIn: accessTokenExpiresInSecs,
|
||||
refreshToken: refreshToken
|
||||
}
|
||||
} catch {
|
||||
throw new ServerError(Status.INTERNAL, 'Could not generate OAuth tokens');
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,7 @@ import bcrypt from 'bcrypt';
|
|||
import deviceCertificateMiddleware from '@/middleware/device-certificate';
|
||||
import consoleStatusVerificationMiddleware from '@/middleware/console-status-verification';
|
||||
import { getPNIDByNNASRefreshToken, getPNIDByUsername } from '@/database';
|
||||
import { generateToken } from '@/util';
|
||||
import { config } from '@/config-manager';
|
||||
import { generateOAuthTokens } from '@/util';
|
||||
import { Device } from '@/models/device';
|
||||
|
||||
const router = express.Router();
|
||||
|
|
@ -153,37 +152,22 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus
|
|||
return;
|
||||
}
|
||||
|
||||
const accessTokenOptions = {
|
||||
system_type: 0x1, // * WiiU
|
||||
token_type: 0x1, // * OAuth Access
|
||||
pid: pnid.pid,
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
try {
|
||||
const systemType = 0x1; // * WiiU
|
||||
const { accessToken, refreshToken, accessTokenExpiresInSecs } = generateOAuthTokens(systemType, pnid);
|
||||
|
||||
const refreshTokenOptions = {
|
||||
system_type: 0x1, // * WiiU
|
||||
token_type: 0x2, // * OAuth Refresh
|
||||
pid: pnid.pid,
|
||||
expire_time: BigInt(Date.now() + (3600 * 1000))
|
||||
};
|
||||
|
||||
const accessTokenBuffer = await generateToken(config.aes_key, accessTokenOptions);
|
||||
const refreshTokenBuffer = await generateToken(config.aes_key, refreshTokenOptions);
|
||||
|
||||
const accessToken = accessTokenBuffer ? accessTokenBuffer.toString('hex') : '';
|
||||
const newRefreshToken = refreshTokenBuffer ? refreshTokenBuffer.toString('hex') : '';
|
||||
|
||||
// TODO - Handle null tokens
|
||||
|
||||
response.send(xmlbuilder.create({
|
||||
OAuth20: {
|
||||
access_token: {
|
||||
token: accessToken,
|
||||
refresh_token: newRefreshToken,
|
||||
expires_in: 3600
|
||||
response.send(xmlbuilder.create({
|
||||
OAuth20: {
|
||||
access_token: {
|
||||
token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
expires_in: accessTokenExpiresInSecs
|
||||
}
|
||||
}
|
||||
}
|
||||
}).commentBefore('WARNING! DO NOT SHARE ANYTHING IN THIS REQUEST OR RESPONSE WITH UNTRUSTED USERS! IT CAN BE USED TO IMPERSONATE YOU AND YOUR CONSOLE, POTENTIALLY GETTING YOU BANNED!').end()); // TODO - This is ugly
|
||||
}).commentBefore('WARNING! DO NOT SHARE ANYTHING IN THIS REQUEST OR RESPONSE WITH UNTRUSTED USERS! IT CAN BE USED TO IMPERSONATE YOU AND YOUR CONSOLE, POTENTIALLY GETTING YOU BANNED!').end()); // TODO - This is ugly
|
||||
} catch {
|
||||
response.status(500);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
37
src/util.ts
37
src/util.ts
|
|
@ -12,7 +12,7 @@ import { sendMail } from '@/mailer';
|
|||
import { config, disabledFeatures } from '@/config-manager';
|
||||
import { TokenOptions } from '@/types/common/token-options';
|
||||
import { Token } from '@/types/common/token';
|
||||
import { IPNID, IPNIDMethods } from '@/types/mongoose/pnid';
|
||||
import { HydratedPNIDDocument, IPNID, IPNIDMethods } from '@/types/mongoose/pnid';
|
||||
import { SafeQs } from '@/types/common/safe-qs';
|
||||
|
||||
let s3: S3;
|
||||
|
|
@ -53,6 +53,39 @@ export function nintendoBase64Encode(decoded: string | Buffer): string {
|
|||
return encoded.replaceAll('+', '.').replaceAll('/', '-').replaceAll('=', '*');
|
||||
}
|
||||
|
||||
export function generateOAuthTokens(systemType: number, pnid: HydratedPNIDDocument) {
|
||||
const accessTokenExpiresInSecs = 60 * 60; // * 1 hour
|
||||
|
||||
const accessTokenOptions = {
|
||||
system_type: systemType,
|
||||
token_type: 0x1, // * OAuth Access
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
expire_time: BigInt(Date.now() + (accessTokenExpiresInSecs * 1000)) // * 1 hour
|
||||
};
|
||||
|
||||
const refreshTokenOptions = {
|
||||
system_type: systemType,
|
||||
token_type: 0x2, // * OAuth Refresh
|
||||
pid: pnid.pid,
|
||||
access_level: pnid.access_level,
|
||||
expire_time: BigInt(Date.now() + (30 * 24 * 60 * 60 * 1000)) // * 30 days
|
||||
};
|
||||
|
||||
const accessToken = generateToken(config.aes_key, accessTokenOptions)?.toString('hex');
|
||||
const refreshToken = generateToken(config.aes_key, refreshTokenOptions)?.toString('hex');
|
||||
|
||||
if (!accessToken) throw new Error('Failed to generate access token');
|
||||
if (!refreshToken) throw new Error('Failed to generate refresh token');
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
accessTokenExpiresInSecs,
|
||||
refreshToken
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function generateToken(key: string, options: TokenOptions): Buffer | null {
|
||||
let dataBuffer = Buffer.alloc(1 + 1 + 4 + 8);
|
||||
|
||||
|
|
@ -234,7 +267,7 @@ export async function sendForgotPasswordEmail(pnid: mongoose.HydratedDocument<IP
|
|||
expire_time: BigInt(Date.now() + (24 * 60 * 60 * 1000)) // * Only valid for 24 hours
|
||||
};
|
||||
|
||||
const tokenBuffer = await generateToken(config.aes_key, tokenOptions);
|
||||
const tokenBuffer = generateToken(config.aes_key, tokenOptions);
|
||||
const passwordResetToken = tokenBuffer ? tokenBuffer.toString('hex') : '';
|
||||
|
||||
// TODO - Handle null token
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user