mirror of
https://github.com/PretendoNetwork/account.git
synced 2026-04-24 23:11:05 -05:00
fix: Seperate token checks by type
Give each expected token type a seperate function and un-export the "generic" one. This required deciding what the "expected" token type is for all endpoints. Possibly contraversial choices are: - API tokens only on the HTTP API (no service tokens) - API tokens only on the gRPC APIs - API tokens only on ExchangeTokenForUserData (I could only find 1 user of this API - BOSS - and they use API tokens here) Notably, there is now no way to get the account server to validate a service token - services must decrypt them locally. We should probably add an API for this for the benefit of third parties.
This commit is contained in:
parent
fc1963363a
commit
b9cf3194fc
|
|
@ -5,8 +5,9 @@ import { nintendoPasswordHash, decryptToken, unpackToken } from '@/util';
|
|||
import { PNID } from '@/models/pnid';
|
||||
import { Server } from '@/models/server';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
import { TokenType } from '@/types/common/token-types';
|
||||
import { config } from '@/config-manager';
|
||||
import { TokenType } from '@/types/common/token-types';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
import type { IDeviceAttribute } from '@/types/mongoose/device-attribute';
|
||||
import type { HydratedServerDocument } from '@/types/mongoose/server';
|
||||
|
|
@ -14,7 +15,6 @@ import type { PNIDProfile } from '@/types/services/nnas/pnid-profile';
|
|||
import type { ConnectionData } from '@/types/services/api/connection-data';
|
||||
import type { ConnectionResponse } from '@/types/services/api/connection-response';
|
||||
import type { DiscordConnectionData } from '@/types/services/api/discord-connection-data';
|
||||
import type { SystemType } from '@/types/common/system-types';
|
||||
|
||||
const connection_string = config.mongoose.connection_string;
|
||||
const options = config.mongoose.options;
|
||||
|
|
@ -106,25 +106,22 @@ export async function getPNIDByBasicAuth(token: string): Promise<HydratedPNIDDoc
|
|||
return pnid;
|
||||
}
|
||||
|
||||
export async function getPNIDByTokenAuth(token: string, allowedTypes?: SystemType[]): Promise<HydratedPNIDDocument | null> {
|
||||
async function getPNIDByOAuthToken(token: string, expectedSystemTypes: SystemType[], expectedTokenType: TokenType): Promise<HydratedPNIDDocument | null> {
|
||||
verifyConnected();
|
||||
|
||||
try {
|
||||
const decryptedToken = decryptToken(Buffer.from(token, 'hex'));
|
||||
const unpackedToken = unpackToken(decryptedToken);
|
||||
|
||||
if (allowedTypes && !allowedTypes.includes(unpackedToken.system_type)) {
|
||||
if (!expectedSystemTypes.includes(unpackedToken.system_type)) {
|
||||
return null;
|
||||
}
|
||||
if (unpackedToken.token_type !== expectedTokenType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pnid = await getPNIDByPID(unpackedToken.pid);
|
||||
|
||||
// TODO - This is a hack. If `allowedTypes` is not set, it is assumed to be a call from our internal API,
|
||||
// and we ignore expiration checks on service tokens. Independent services should expire their own tokens
|
||||
if (unpackedToken.token_type === TokenType.IndependentService && !allowedTypes) {
|
||||
return pnid;
|
||||
}
|
||||
|
||||
if (pnid) {
|
||||
const expireTime = Math.floor((Number(unpackedToken.expire_time) / 1000));
|
||||
|
||||
|
|
@ -141,6 +138,22 @@ export async function getPNIDByTokenAuth(token: string, allowedTypes?: SystemTyp
|
|||
}
|
||||
}
|
||||
|
||||
export async function getPNIDByNNASAccessToken(token: string): Promise<HydratedPNIDDocument | null> {
|
||||
return getPNIDByOAuthToken(token, [SystemType.WUP, SystemType.CTR], TokenType.OAuthAccess);
|
||||
}
|
||||
|
||||
export async function getPNIDByNNASRefreshToken(token: string): Promise<HydratedPNIDDocument | null> {
|
||||
return getPNIDByOAuthToken(token, [SystemType.WUP, SystemType.CTR], TokenType.OAuthRefresh);
|
||||
}
|
||||
|
||||
export async function getPNIDByAPIAccessToken(token: string): Promise<HydratedPNIDDocument | null> {
|
||||
return getPNIDByOAuthToken(token, [SystemType.API], TokenType.OAuthAccess);
|
||||
}
|
||||
|
||||
export async function getPNIDByAPIRefreshToken(token: string): Promise<HydratedPNIDDocument | null> {
|
||||
return getPNIDByOAuthToken(token, [SystemType.API], TokenType.OAuthRefresh);
|
||||
}
|
||||
|
||||
export async function getPNIDProfileJSONByPID(pid: number): Promise<PNIDProfile | null> {
|
||||
verifyConnected();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getValueFromHeaders } from '@/util';
|
||||
import { getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByAPIAccessToken } from '@/database';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
import type express from 'express';
|
||||
async function APIMiddleware(request: express.Request, _response: express.Response, next: express.NextFunction): Promise<void> {
|
||||
|
|
@ -11,7 +11,7 @@ async function APIMiddleware(request: express.Request, _response: express.Respon
|
|||
|
||||
try {
|
||||
const token = authHeader.split(' ')[1];
|
||||
const pnid = await getPNIDByTokenAuth(token);
|
||||
const pnid = await getPNIDByAPIAccessToken(token);
|
||||
|
||||
request.pnid = pnid;
|
||||
} catch (error: any) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import xmlbuilder from 'xmlbuilder';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
import { getValueFromHeaders } from '@/util';
|
||||
import { getPNIDByBasicAuth, getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByBasicAuth, getPNIDByNNASAccessToken } from '@/database';
|
||||
import type express from 'express';
|
||||
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
|
||||
|
|
@ -24,8 +23,7 @@ async function PNIDMiddleware(request: express.Request, response: express.Respon
|
|||
if (type === 'Basic' && request.path.includes('v1/api/people/@me/devices')) {
|
||||
pnid = await getPNIDByBasicAuth(token);
|
||||
} else if (type === 'Bearer') {
|
||||
// TODO - This "accepted types list" is mostly a hack. Change this
|
||||
pnid = await getPNIDByTokenAuth(token, [SystemType.WUP, SystemType.CTR]);
|
||||
pnid = await getPNIDByNNASAccessToken(token);
|
||||
}
|
||||
|
||||
if (!pnid) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import express from 'express';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
|
||||
import { nintendoPasswordHash, generateToken } from '@/util';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
import { TokenType } from '@/types/common/token-types';
|
||||
|
|
@ -89,7 +89,7 @@ router.post('/', async (request: express.Request, response: express.Response): P
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
pnid = await getPNIDByTokenAuth(refreshToken);
|
||||
pnid = await getPNIDByAPIRefreshToken(refreshToken);
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).json({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByAPIAccessToken } from '@/database';
|
||||
import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags';
|
||||
import { config } from '@/config-manager';
|
||||
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc';
|
||||
|
|
@ -10,7 +10,7 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData
|
|||
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token');
|
||||
}
|
||||
|
||||
const pnid = await getPNIDByTokenAuth(request.token);
|
||||
const pnid = await getPNIDByAPIAccessToken(request.token);
|
||||
|
||||
if (!pnid) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByAPIAccessToken } from '@/database';
|
||||
import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags';
|
||||
import { config } from '@/config-manager';
|
||||
import { Device } from '@/models/device';
|
||||
|
|
@ -11,7 +11,7 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData
|
|||
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token');
|
||||
}
|
||||
|
||||
const pnid = await getPNIDByTokenAuth(request.token);
|
||||
const pnid = await getPNIDByAPIAccessToken(request.token);
|
||||
|
||||
if (!pnid) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByAPIAccessToken } from '@/database';
|
||||
import type { ServerMiddlewareCall, CallContext } from 'nice-grpc';
|
||||
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ export async function* authenticationMiddleware<Request, Response>(
|
|||
let pnid = null;
|
||||
|
||||
if (token) {
|
||||
pnid = await getPNIDByTokenAuth(token);
|
||||
pnid = await getPNIDByAPIAccessToken(token);
|
||||
}
|
||||
|
||||
if (!pnid && TOKEN_REQUIRED_PATHS.includes(call.method.path)) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
|
||||
import { nintendoPasswordHash, generateToken } from '@/util';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
import { TokenType } from '@/types/common/token-types';
|
||||
|
|
@ -45,7 +45,7 @@ export async function login(request: LoginRequest): Promise<DeepPartial<LoginRes
|
|||
throw new ServerError(Status.INVALID_ARGUMENT, 'Password is incorrect');
|
||||
}
|
||||
} else {
|
||||
pnid = await getPNIDByTokenAuth(refreshToken!); // * We know refreshToken will never be null here
|
||||
pnid = await getPNIDByAPIRefreshToken(refreshToken!); // * We know refreshToken will never be null here
|
||||
|
||||
if (!pnid) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import { getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByAPIAccessToken } from '@/database';
|
||||
import type { ServerMiddlewareCall, CallContext } from 'nice-grpc';
|
||||
import type { HydratedPNIDDocument } from '@/types/mongoose/pnid';
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ export async function* authenticationMiddleware<Request, Response>(
|
|||
let pnid = null;
|
||||
|
||||
if (token) {
|
||||
pnid = await getPNIDByTokenAuth(token);
|
||||
pnid = await getPNIDByAPIAccessToken(token);
|
||||
}
|
||||
|
||||
if (!pnid && TOKEN_REQUIRED_PATHS.includes(call.method.path)) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Status, ServerError } from 'nice-grpc';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { getPNIDByUsername, getPNIDByTokenAuth } from '@/database';
|
||||
import { getPNIDByUsername, getPNIDByAPIRefreshToken } from '@/database';
|
||||
import { nintendoPasswordHash, generateToken } from '@/util';
|
||||
import { config } from '@/config-manager';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
|
|
@ -45,7 +45,7 @@ export async function login(request: LoginRequest): Promise<DeepPartial<LoginRes
|
|||
throw new ServerError(Status.INVALID_ARGUMENT, 'Password is incorrect');
|
||||
}
|
||||
} else {
|
||||
pnid = await getPNIDByTokenAuth(refreshToken!); // * We know refreshToken will never be null here
|
||||
pnid = await getPNIDByAPIRefreshToken(refreshToken!); // * We know refreshToken will never be null here
|
||||
|
||||
if (!pnid) {
|
||||
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid or missing refresh token');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import xmlbuilder from 'xmlbuilder';
|
|||
import bcrypt from 'bcrypt';
|
||||
import deviceCertificateMiddleware from '@/middleware/device-certificate';
|
||||
import consoleStatusVerificationMiddleware from '@/middleware/console-status-verification';
|
||||
import { getPNIDByTokenAuth, getPNIDByUsername } from '@/database';
|
||||
import { getPNIDByNNASRefreshToken, getPNIDByUsername } from '@/database';
|
||||
import { generateToken } from '@/util';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
import { TokenType } from '@/types/common/token-types';
|
||||
|
|
@ -90,7 +90,7 @@ router.post('/access_token/generate', deviceCertificateMiddleware, consoleStatus
|
|||
}
|
||||
|
||||
try {
|
||||
pnid = await getPNIDByTokenAuth(refreshToken);
|
||||
pnid = await getPNIDByNNASRefreshToken(refreshToken);
|
||||
|
||||
if (!pnid) {
|
||||
response.status(400).send(xmlbuilder.create({
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user