mirror of
https://github.com/HeyPuter/puter.git
synced 2026-01-16 01:50:59 -06:00
Add TokenService and test utility
This commit is contained in:
@@ -198,6 +198,9 @@ const install = async ({ services, app }) => {
|
||||
|
||||
const { Emailservice } = require('./services/EmailService');
|
||||
services.registerService('email', Emailservice);
|
||||
|
||||
const { TokenService } = require('./services/auth/TokenService');
|
||||
services.registerService('token', TokenService);
|
||||
}
|
||||
|
||||
const install_legacy = async ({ services }) => {
|
||||
|
||||
172
packages/backend/src/services/auth/TokenService.js
Normal file
172
packages/backend/src/services/auth/TokenService.js
Normal file
@@ -0,0 +1,172 @@
|
||||
const BaseService = require("../BaseService");
|
||||
|
||||
def = o => {
|
||||
for ( let k in o ) {
|
||||
if ( typeof o[k] === 'string' ) {
|
||||
o[k] = { short: o[k] };
|
||||
}
|
||||
}
|
||||
return {
|
||||
fullkey_to_info: o,
|
||||
short_to_fullkey: Object.keys(o).reduce((acc, key) => {
|
||||
acc[o[key].short] = key;
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
|
||||
defv = o => {
|
||||
return {
|
||||
to_short: o,
|
||||
to_long: Object.keys(o).reduce((acc, key) => {
|
||||
acc[o[key]] = key;
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
};
|
||||
|
||||
const compression = {
|
||||
auth: def({
|
||||
uuid: 'u',
|
||||
type: {
|
||||
short: 't',
|
||||
values: defv({
|
||||
'session': 's',
|
||||
'access-token': 't',
|
||||
'app-under-user': 'au',
|
||||
}),
|
||||
},
|
||||
user_uid: 'uu',
|
||||
app_uid: 'au',
|
||||
}),
|
||||
};
|
||||
|
||||
class TokenService extends BaseService {
|
||||
static MODULES = {
|
||||
jwt: require('jsonwebtoken'),
|
||||
};
|
||||
|
||||
_construct () {
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
_init () {
|
||||
// TODO: move to service config
|
||||
this.secret = this.global_config.jwt_secret;
|
||||
}
|
||||
|
||||
sign (scope, payload, options) {
|
||||
const require = this.require;
|
||||
|
||||
const jwt = require('jwt');
|
||||
const secret = this.secret;
|
||||
|
||||
const context = this.compression[scope];
|
||||
const compressed_payload = this._compress_payload(context, payload);
|
||||
|
||||
return jwt.sign(compressed_payload, secret, options);
|
||||
}
|
||||
|
||||
verify (scope, token) {
|
||||
const require = this.require;
|
||||
|
||||
const jwt = require('jwt');
|
||||
const secret = this.secret;
|
||||
|
||||
const context = this.compression[scope];
|
||||
const payload = jwt.verify(token, secret);
|
||||
|
||||
return this._decompress_payload(context, payload);
|
||||
}
|
||||
|
||||
_compress_payload (context, payload) {
|
||||
const fullkey_to_info = context.fullkey_to_info;
|
||||
|
||||
const compressed = {};
|
||||
|
||||
for ( let fullkey in payload ) {
|
||||
if ( ! fullkey_to_info[fullkey] ) {
|
||||
compressed[fullkey] = payload[fullkey];
|
||||
continue;
|
||||
}
|
||||
|
||||
let k = fullkey, v = payload[fullkey];
|
||||
const compress_info = fullkey_to_info[fullkey];
|
||||
|
||||
if ( compress_info.short ) k = compress_info.short;
|
||||
if ( compress_info.values && compress_info.values.to_short[v] ) {
|
||||
v = compress_info.values.to_short[v];
|
||||
}
|
||||
|
||||
compressed[k] = v;
|
||||
}
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
_decompress_payload (context, payload) {
|
||||
const fullkey_to_info = context.fullkey_to_info;
|
||||
const short_to_fullkey = context.short_to_fullkey;
|
||||
|
||||
const decompressed = {};
|
||||
|
||||
for ( let short in payload ) {
|
||||
if ( ! short_to_fullkey[short] ) {
|
||||
decompressed[short] = payload[short];
|
||||
continue;
|
||||
}
|
||||
|
||||
let k = short, v = payload[short];
|
||||
const fullkey = short_to_fullkey[short];
|
||||
const compress_info = fullkey_to_info[fullkey];
|
||||
|
||||
|
||||
if ( compress_info.short ) k = fullkey;
|
||||
if ( compress_info.values && compress_info.values.to_long[v] ) {
|
||||
v = compress_info.values.to_long[v];
|
||||
}
|
||||
|
||||
decompressed[k] = v;
|
||||
}
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
_test ({ assert }) {
|
||||
// Test compression
|
||||
{
|
||||
const context = this.compression.auth;
|
||||
const payload = {
|
||||
uuid: '123',
|
||||
type: 'session',
|
||||
user_uid: '456',
|
||||
app_uid: '789',
|
||||
};
|
||||
|
||||
const compressed = this._compress_payload(context, payload);
|
||||
assert(() => compressed.u === '123');
|
||||
assert(() => compressed.t === 's');
|
||||
assert(() => compressed.uu === '456');
|
||||
assert(() => compressed.au === '789');
|
||||
}
|
||||
|
||||
// Test decompression
|
||||
{
|
||||
const context = this.compression.auth;
|
||||
const payload = {
|
||||
u: '123',
|
||||
t: 's',
|
||||
uu: '456',
|
||||
au: '789',
|
||||
};
|
||||
|
||||
const decompressed = this._decompress_payload(context, payload);
|
||||
assert(() => decompressed.uuid === '123');
|
||||
assert(() => decompressed.type === 'session');
|
||||
assert(() => decompressed.user_uid === '456');
|
||||
assert(() => decompressed.app_uid === '789');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { TokenService };
|
||||
127
packages/backend/tools/test.js
Normal file
127
packages/backend/tools/test.js
Normal file
@@ -0,0 +1,127 @@
|
||||
const { AdvancedBase } = require("@heyputer/puter-js-common");
|
||||
const CoreModule = require("../src/CoreModule");
|
||||
const { Context } = require("../src/util/context");
|
||||
|
||||
class TestKernel extends AdvancedBase {
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this.modules = [];
|
||||
|
||||
this.logfn_ = (...a) => a;
|
||||
}
|
||||
|
||||
add_module (module) {
|
||||
this.modules.push(module);
|
||||
}
|
||||
|
||||
boot () {
|
||||
const { consoleLogManager } = require('../src/util/consolelog');
|
||||
consoleLogManager.initialize_proxy_methods();
|
||||
|
||||
consoleLogManager.decorate_all(({ manager, replace }, ...a) => {
|
||||
replace(...this.logfn_(...a));
|
||||
});
|
||||
|
||||
const { Container } = require('../src/services/Container');
|
||||
|
||||
const services = new Container();
|
||||
this.services = services;
|
||||
// app.set('services', services);
|
||||
|
||||
const root_context = Context.create({
|
||||
services,
|
||||
}, 'app');
|
||||
globalThis.root_context = root_context;
|
||||
|
||||
root_context.arun(async () => {
|
||||
await this._install_modules();
|
||||
// await this._boot_services();
|
||||
});
|
||||
|
||||
// Error.stackTraceLimit = Infinity;
|
||||
Error.stackTraceLimit = 200;
|
||||
}
|
||||
|
||||
async _install_modules () {
|
||||
const { services } = this;
|
||||
|
||||
for ( const module of this.modules ) {
|
||||
await module.install(Context.get());
|
||||
}
|
||||
|
||||
// Real kernel initializes services here, but in this test kernel
|
||||
// we don't initialize any services.
|
||||
|
||||
// Real kernel adds legacy services here but these will break
|
||||
// the test kernel.
|
||||
|
||||
services.ready.resolve();
|
||||
|
||||
// provide services to helpers
|
||||
// const { tmp_provide_services } = require('../src/helpers');
|
||||
// tmp_provide_services(services);
|
||||
}
|
||||
}
|
||||
|
||||
const k = new TestKernel();
|
||||
k.add_module(new CoreModule());
|
||||
k.boot();
|
||||
|
||||
const do_after_tests_ = [];
|
||||
|
||||
// const do_after_tests = (fn) => {
|
||||
// do_after_tests_.push(fn);
|
||||
// };
|
||||
const repeat_after = (fn) => {
|
||||
fn();
|
||||
do_after_tests_.push(fn);
|
||||
};
|
||||
|
||||
let total_passed = 0;
|
||||
let total_failed = 0;
|
||||
|
||||
for ( const name in k.services.instances_ ) {
|
||||
console.log('name', name)
|
||||
const ins = k.services.instances_[name];
|
||||
ins.construct();
|
||||
if ( ! ins._test || typeof ins._test !== 'function' ) {
|
||||
continue;
|
||||
}
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
repeat_after(() => {
|
||||
console.log(`\x1B[33;1m=== [ Service :: ${name} ] ===\x1B[0m`);
|
||||
});
|
||||
|
||||
const testapi = {
|
||||
assert: (condition, name) => {
|
||||
name = name || condition.toString();
|
||||
if ( condition() ) {
|
||||
passed++;
|
||||
repeat_after(() => console.log(`\x1B[32;1m ✔ ${name}\x1B[0m`));
|
||||
} else {
|
||||
failed++;
|
||||
repeat_after(() => console.log(`\x1B[31;1m ✘ ${name}\x1B[0m`));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ins._test(testapi);
|
||||
|
||||
total_passed += passed;
|
||||
total_failed += failed;
|
||||
}
|
||||
|
||||
console.log(`\x1B[36;1m<===\x1B[0m ` +
|
||||
'ASSERTION OUTPUTS ARE REPEATED BELOW' +
|
||||
` \x1B[36;1m===>\x1B[0m`);
|
||||
|
||||
for ( const fn of do_after_tests_ ) {
|
||||
fn();
|
||||
}
|
||||
|
||||
console.log(`\x1B[36;1m=== [ Summary ] ===\x1B[0m`);
|
||||
console.log(`Passed: ${total_passed}`);
|
||||
console.log(`Failed: ${total_failed}`);
|
||||
Reference in New Issue
Block a user