rr-locker-api / src /services /amapiService.js
MD.Riad Rayhan
Node.js + Socket.IO backend (migrated from FastAPI)
839aef2 verified
Raw
History Blame Contribute Delete
6.57 kB
/**
* Android Management API (AMAPI) service — port of amapi_service.py using the
* Node `googleapis` client. Used for Google Cloud DPC QR provisioning and
* enterprise/device administration.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { google } from 'googleapis';
import { settings } from '../config.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCOPES = ['https://www.googleapis.com/auth/androidmanagement'];
const CLOUD_DPC_COMPONENT = 'com.google.android.apps.work.clouddpc/.receivers.CloudDeviceAdminReceiver';
const CLOUD_DPC_SIGNATURE = 'I5YvS0O5hXY46mb01BlRjq4oJJGs2kuUcHvVkAPEXlg';
const CLOUD_DPC_DOWNLOAD = 'https://play.google.com/managed/downloadManagingApp?identifier=setup';
function localServiceAccountPaths() {
return [
path.resolve(__dirname, '..', '..', 'service-account.json'),
path.resolve(__dirname, '..', '..', '..', 'backend', 'service-account.json'),
'service-account.json',
];
}
function loadCredentials() {
const credValue = settings.AMAPI_SERVICE_ACCOUNT_JSON;
for (const p of localServiceAccountPaths()) {
if (fs.existsSync(p)) {
return JSON.parse(fs.readFileSync(p, 'utf8'));
}
}
if (!credValue) {
throw new Error('AMAPI_SERVICE_ACCOUNT_JSON not configured.');
}
if (fs.existsSync(credValue)) {
return JSON.parse(fs.readFileSync(credValue, 'utf8'));
}
// base64-encoded JSON
try {
return JSON.parse(Buffer.from(credValue, 'base64').toString('utf8'));
} catch { /* fall through */ }
// raw JSON
try {
return JSON.parse(credValue);
} catch {
throw new Error('AMAPI_SERVICE_ACCOUNT_JSON is not a valid file path, base64 JSON, or raw JSON.');
}
}
async function getService() {
const credentials = loadCredentials();
const auth = new google.auth.GoogleAuth({ credentials, scopes: SCOPES });
const authClient = await auth.getClient();
return google.androidmanagement({ version: 'v1', auth: authClient });
}
export function isConfigured() {
if (settings.AMAPI_SERVICE_ACCOUNT_JSON && settings.AMAPI_PROJECT_ID) return true;
if (settings.AMAPI_PROJECT_ID) {
return localServiceAccountPaths().some((p) => fs.existsSync(p));
}
return false;
}
export function getEnterpriseName() {
return settings.AMAPI_ENTERPRISE_NAME || null;
}
export async function createEnterprise() {
const service = await getService();
const { data } = await service.enterprises.create({
projectId: settings.AMAPI_PROJECT_ID,
agreementAccepted: true,
requestBody: { enterpriseDisplayName: 'RR Locker EMI Finance' },
});
return { enterprise_name: data.name || '', enterprise: data };
}
export async function createPolicy(enterpriseName, policyName = 'emi-locker-policy') {
const service = await getService();
const policy = {
advancedSecurityOverrides: {
untrustedAppsPolicy: 'ALLOW_INSTALL_DEVICE_WIDE',
googlePlayProtectVerifyApps: 'VERIFY_APPS_USER_CHOICE',
developerSettings: 'DEVELOPER_SETTINGS_ALLOWED',
},
factoryResetDisabled: true,
safeBootDisabled: true,
screenCaptureDisabled: true,
addUserDisabled: true,
removeUserDisabled: true,
modifyAccountsDisabled: false,
systemUpdate: { type: 'WINDOWED', startMinutes: 120, endMinutes: 300 },
skipFirstUseHintsEnabled: true,
adjustVolumeDisabled: false,
funDisabled: true,
networkEscapeHatchEnabled: true,
playStoreMode: 'BLACKLIST',
};
const { data } = await service.enterprises.policies.patch({
name: `${enterpriseName}/policies/${policyName}`,
requestBody: policy,
});
return data;
}
export async function createEnrollmentToken(enterpriseName, policyName = 'emi-locker-policy') {
const service = await getService();
const { data } = await service.enterprises.enrollmentTokens.create({
parent: enterpriseName,
requestBody: {
policyName: `${enterpriseName}/policies/${policyName}`,
duration: '86400s',
allowPersonalUsage: 'PERSONAL_USAGE_DISALLOWED',
oneTimeOnly: false,
},
});
return data;
}
export function generateQrPayload(tokenValue) {
return {
'android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME': CLOUD_DPC_COMPONENT,
'android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM': CLOUD_DPC_SIGNATURE,
'android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION': CLOUD_DPC_DOWNLOAD,
'android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE': {
'com.google.android.apps.work.clouddpc.EXTRA_ENROLLMENT_TOKEN': tokenValue,
},
'android.app.extra.PROVISIONING_SKIP_ENCRYPTION': true,
'android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED': true,
};
}
export function generateQrString(tokenValue) {
return JSON.stringify(generateQrPayload(tokenValue));
}
export async function listDevices(enterpriseName) {
const service = await getService();
const { data } = await service.enterprises.devices.list({ parent: enterpriseName });
return data.devices || [];
}
export async function deleteDevice(deviceName) {
const service = await getService();
try {
await service.enterprises.devices.delete({ name: deviceName });
return true;
} catch {
return false;
}
}
export async function cleanupAllDevices(enterpriseName) {
const devices = await listDevices(enterpriseName);
let deleted = 0;
let failed = 0;
for (const device of devices) {
if (device.name) {
// eslint-disable-next-line no-await-in-loop
if (await deleteDevice(device.name)) deleted += 1; else failed += 1;
}
}
return { total: devices.length, deleted, failed };
}
export async function createNewEnterprise() {
const service = await getService();
const { data } = await service.enterprises.create({
projectId: settings.AMAPI_PROJECT_ID,
agreementAccepted: true,
requestBody: { enterpriseDisplayName: 'RR Locker EMI Finance' },
});
const newName = data.name || '';
settings.AMAPI_ENTERPRISE_NAME = newName;
try {
await createPolicy(newName);
} catch { /* best effort */ }
return { enterprise_name: newName, enterprise: data };
}
export default {
isConfigured,
getEnterpriseName,
createEnterprise,
createPolicy,
createEnrollmentToken,
generateQrPayload,
generateQrString,
listDevices,
deleteDevice,
cleanupAllDevices,
createNewEnterprise,
};