Merge pull request #329 from PretendoNetwork/feat/bypass-account-deletion
Some checks failed
Build and Publish Docker Image / Build and Publish Docker Image (amd64) (push) Has been cancelled
Build and Publish Docker Image / Build and Publish Docker Image (arm64) (push) Has been cancelled

Upgrade gRPC and ESLint, implement deletion bypass, add indexes
This commit is contained in:
William Oldham 2026-04-20 21:58:25 +01:00 committed by GitHub
commit b2768be21c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 911 additions and 654 deletions

1457
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@
"@aws-sdk/client-s3": "^3.657.0",
"@aws-sdk/client-ses": "^3.515.0",
"@inquirer/prompts": "^7.2.0",
"@pretendonetwork/grpc": "^2.2.3",
"@pretendonetwork/grpc": "^2.5.2",
"bcrypt": "^5.0.0",
"buffer-crc32": "^0.2.13",
"colors": "^1.4.0",
@ -69,7 +69,7 @@
},
"devDependencies": {
"@hcaptcha/types": "^1.0.3",
"@pretendonetwork/eslint-config": "^0.0.8",
"@pretendonetwork/eslint-config": "^0.1.4",
"@types/bcrypt": "^5.0.0",
"@types/buffer-crc32": "^0.2.2",
"@types/cors": "^2.8.13",

View File

@ -130,6 +130,11 @@ const PNIDSchema = new Schema<IPNID, PNIDModel, IPNIDMethods>({
}
}, { id: false });
PNIDSchema.index({ pid: 1 });
PNIDSchema.index({ usernameLower: 1 });
// Used for the admin panel querying
PNIDSchema.index({ 'pid': 1, 'username': 1, 'connections.discord.id': 1 });
PNIDSchema.plugin(uniqueValidator, { message: '{PATH} already in use.' });
/*

View File

@ -1,9 +1,9 @@
import fs from 'node:fs/promises';
import { z } from 'zod';
import mongoose from 'mongoose';
import { config, disabledFeatures } from './config-manager';
import { LOG_INFO, LOG_WARN } from './logger';
import { Server } from './models/server';
import { config, disabledFeatures } from '@/config-manager';
import { LOG_INFO, LOG_WARN } from '@/logger';
import { Server } from '@/models/server';
// Provisioning has a couple edgecases:
// - It will only update existing entries, will not add new one

View File

@ -16,7 +16,7 @@ import api from '@/services/api';
import localcdn from '@/services/local-cdn';
import assets from '@/services/assets';
import { config, disabledFeatures } from '@/config-manager';
import { startProvisioner } from './provisioning';
import { startProvisioner } from '@/provisioning';
process.title = 'Pretendo - Account';
process.on('uncaughtException', (err, origin) => {

View File

@ -1,7 +1,7 @@
import { Status, ServerError } from 'nice-grpc';
import { getPNIDByPID } from '@/database';
import { sendPNIDDeletedEmail } from '@/util';
import { LOG_ERROR } from '@/logger';
import { LOG_ERROR, LOG_INFO } from '@/logger';
import type { DeleteAccountRequest, DeleteAccountResponse } from '@pretendonetwork/grpc/account/v2/delete_account_rpc';
export async function deleteAccount(request: DeleteAccountRequest): Promise<DeleteAccountResponse> {
@ -15,14 +15,26 @@ export async function deleteAccount(request: DeleteAccountRequest): Promise<Dele
}
try {
LOG_INFO(`Deleting PNID ${pnid.pid} (bypass grace period=${request.bypassGracePeriod})`);
const email = pnid.email.address;
pnid.markForDeletion();
if (request.bypassGracePeriod) {
await pnid.scrub();
} else {
pnid.markForDeletion();
}
await pnid.save();
await sendPNIDDeletedEmail(email, pnid.username);
if (request.bypassGracePeriod) {
LOG_INFO(`PNID ${pnid.pid} deleted immediately (bypassed grace period)`);
} else {
LOG_INFO(`PNID ${pnid.pid} marked for deletion (will be deleted after grace period)`);
}
} catch (error) {
LOG_ERROR(`Deleting PNID ${error}`);
LOG_ERROR(`Error deleting PNID ${pnid.pid}: ${error}`);
}
return {

View File

@ -2,11 +2,9 @@ import { Status, ServerError } from 'nice-grpc';
import { getPNIDByAPIAccessToken } from '@/database';
import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags';
import { config } from '@/config-manager';
import { Device } from '@/models/device';
import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/v2/get_user_data_rpc';
import type { ExchangeTokenForUserDataRequest } from '@pretendonetwork/grpc/account/v2/exchange_token_for_user_data';
import type { ExchangeTokenForUserDataRequest, ExchangeTokenForUserDataResponse } from '@pretendonetwork/grpc/account/v2/exchange_token_for_user_data_rpc';
export async function exchangeTokenForUserData(request: ExchangeTokenForUserDataRequest): Promise<GetUserDataResponse> {
export async function exchangeTokenForUserData(request: ExchangeTokenForUserDataRequest): Promise<ExchangeTokenForUserDataResponse> {
if (!request.token.trim()) {
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token');
}
@ -17,18 +15,6 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData
throw new ServerError(Status.INVALID_ARGUMENT, 'Invalid token');
}
const devices = (await Device.find({
linked_pids: pnid.pid
})).map((device) => {
return {
model: device.get('model'), // ".model" gives the Mongoose model...
serial: device.serial,
linkedPids: device.linked_pids,
accessLevel: device.access_level,
serverAccessLevel: device.server_access_level
};
});
return {
deleted: pnid.deleted || pnid.marked_for_deletion,
pid: pnid.pid,
@ -70,7 +56,6 @@ export async function exchangeTokenForUserData(request: ExchangeTokenForUserData
updateBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_BOSS_FILES),
deleteBossFiles: pnid.hasPermission(PNID_PERMISSION_FLAGS.DELETE_BOSS_FILES),
updatePnidPermissions: pnid.hasPermission(PNID_PERMISSION_FLAGS.UPDATE_PNID_PERMISSIONS)
},
linkedDevices: devices
}
};
}

View File

@ -19,6 +19,7 @@ export async function getUserData(request: GetUserDataRequest): Promise<GetUserD
linked_pids: pnid.pid
})).map((device) => {
return {
deviceId: device.device_id,
model: device.get('model'), // ".model" gives the Mongoose model...
serial: device.serial,
linkedPids: device.linked_pids,

View File

@ -1,15 +1,55 @@
import { ServerError, Status } from 'nice-grpc';
import { getUserData } from '@/services/grpc/account/v2/get-user-data';
import { getNEXPassword } from '@/services/grpc/account/v2/get-nex-password';
import { getNEXData } from '@/services/grpc/account/v2/get-nex-data';
import { updatePNIDPermissions } from '@/services/grpc/account/v2/update-pnid-permissions';
import { exchangeTokenForUserData } from '@/services/grpc/account/v2/exchange-token-for-user-data';
import { deleteAccount } from '@/services/grpc/account/v2/delete-account';
import type { ServiceImplementation } from 'nice-grpc';
import type { AccountServiceDefinition } from '@pretendonetwork/grpc/account/v2/account_service';
export const accountServiceImplementationV2 = {
const notImplemented = (): never => {
throw new ServerError(Status.UNIMPLEMENTED, 'Not implemented');
};
export const accountServiceImplementationV2: ServiceImplementation<AccountServiceDefinition> = {
getUserData,
getNEXPassword,
getNEXData,
updatePNIDPermissions,
exchangeTokenForUserData,
deleteAccount
deleteAccount,
// The following methods are not yet implemented
createAuditLogComment: notImplemented,
createBanComment: notImplemented,
createServer: notImplemented,
deleteServer: notImplemented,
deletePNID: notImplemented,
exchangeIndependentServiceTokenForUserData: notImplemented,
exchangeNEXTokenForUserData: notImplemented,
exchangeOAuthTokenForUserData: notImplemented,
exchangePasswordResetTokenForUserData: notImplemented,
getBan: notImplemented,
getDevice: notImplemented,
getNEXAccount: notImplemented,
getPNID: notImplemented,
getServer: notImplemented,
getPNIDs: notImplemented,
issueBan: notImplemented,
listAuditLogComments: notImplemented,
listBans: notImplemented,
listDevices: notImplemented,
listNEXAccounts: notImplemented,
listPNIDs: notImplemented,
listServers: notImplemented,
listAuditLogs: notImplemented,
listBanComments: notImplemented,
pardonBan: notImplemented,
updateBan: notImplemented,
updateDevice: notImplemented,
updateNEXAccount: notImplemented,
updateServer: notImplemented,
updatePNID: notImplemented,
validateIndependentServiceToken: notImplemented
};

View File

@ -1,10 +1,9 @@
import { Status, ServerError } from 'nice-grpc';
import { getPNIDByPID } from '@/database';
import { PNID_PERMISSION_FLAGS } from '@/types/common/permission-flags';
import type { UpdatePNIDPermissionsRequest } from '@pretendonetwork/grpc/account/v2/update_pnid_permissions';
import type { Empty } from '@pretendonetwork/grpc/google/protobuf/empty';
import type { UpdatePNIDPermissionsRequest, UpdatePNIDPermissionsResponse } from '@pretendonetwork/grpc/account/v2/update_pnid_permissions_rpc';
export async function updatePNIDPermissions(request: UpdatePNIDPermissionsRequest): Promise<Empty> {
export async function updatePNIDPermissions(request: UpdatePNIDPermissionsRequest): Promise<UpdatePNIDPermissionsResponse> {
const pnid = await getPNIDByPID(request.pid);
if (!pnid) {

View File

@ -11,11 +11,11 @@ import { SystemType } from '@/types/common/system-types';
import { TokenType } from '@/types/common/token-types';
import { config, disabledFeatures } from '@/config-manager';
import { LOG_ERROR } from '@/logger';
import type { IncomingHttpHeaders } from 'node:http';
import type { ParsedQs } from 'qs';
import type mongoose from 'mongoose';
import type express from 'express';
import type { ObjectCannedACL } from '@aws-sdk/client-s3';
import type { IncomingHttpHeaders } from 'node:http';
import type { TokenOptions } from '@/types/common/token-options';
import type { Token } from '@/types/common/token';
import type { IPNID, IPNIDMethods } from '@/types/mongoose/pnid';