Skip to content

Commit c1e4eee

Browse files
committed
Add TokenService and test utility
1 parent d2de46e commit c1e4eee

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed

packages/backend/src/CoreModule.js

+3
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ const install = async ({ services, app }) => {
198198

199199
const { Emailservice } = require('./services/EmailService');
200200
services.registerService('email', Emailservice);
201+
202+
const { TokenService } = require('./services/auth/TokenService');
203+
services.registerService('token', TokenService);
201204
}
202205

203206
const install_legacy = async ({ services }) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
const BaseService = require("../BaseService");
2+
3+
def = o => {
4+
for ( let k in o ) {
5+
if ( typeof o[k] === 'string' ) {
6+
o[k] = { short: o[k] };
7+
}
8+
}
9+
return {
10+
fullkey_to_info: o,
11+
short_to_fullkey: Object.keys(o).reduce((acc, key) => {
12+
acc[o[key].short] = key;
13+
return acc;
14+
}, {}),
15+
};
16+
}
17+
18+
defv = o => {
19+
return {
20+
to_short: o,
21+
to_long: Object.keys(o).reduce((acc, key) => {
22+
acc[o[key]] = key;
23+
return acc;
24+
}, {}),
25+
};
26+
};
27+
28+
const compression = {
29+
auth: def({
30+
uuid: 'u',
31+
type: {
32+
short: 't',
33+
values: defv({
34+
'session': 's',
35+
'access-token': 't',
36+
'app-under-user': 'au',
37+
}),
38+
},
39+
user_uid: 'uu',
40+
app_uid: 'au',
41+
}),
42+
};
43+
44+
class TokenService extends BaseService {
45+
static MODULES = {
46+
jwt: require('jsonwebtoken'),
47+
};
48+
49+
_construct () {
50+
this.compression = compression;
51+
}
52+
53+
_init () {
54+
// TODO: move to service config
55+
this.secret = this.global_config.jwt_secret;
56+
}
57+
58+
sign (scope, payload, options) {
59+
const require = this.require;
60+
61+
const jwt = require('jwt');
62+
const secret = this.secret;
63+
64+
const context = this.compression[scope];
65+
const compressed_payload = this._compress_payload(context, payload);
66+
67+
return jwt.sign(compressed_payload, secret, options);
68+
}
69+
70+
verify (scope, token) {
71+
const require = this.require;
72+
73+
const jwt = require('jwt');
74+
const secret = this.secret;
75+
76+
const context = this.compression[scope];
77+
const payload = jwt.verify(token, secret);
78+
79+
return this._decompress_payload(context, payload);
80+
}
81+
82+
_compress_payload (context, payload) {
83+
const fullkey_to_info = context.fullkey_to_info;
84+
85+
const compressed = {};
86+
87+
for ( let fullkey in payload ) {
88+
if ( ! fullkey_to_info[fullkey] ) {
89+
compressed[fullkey] = payload[fullkey];
90+
continue;
91+
}
92+
93+
let k = fullkey, v = payload[fullkey];
94+
const compress_info = fullkey_to_info[fullkey];
95+
96+
if ( compress_info.short ) k = compress_info.short;
97+
if ( compress_info.values && compress_info.values.to_short[v] ) {
98+
v = compress_info.values.to_short[v];
99+
}
100+
101+
compressed[k] = v;
102+
}
103+
104+
return compressed;
105+
}
106+
107+
_decompress_payload (context, payload) {
108+
const fullkey_to_info = context.fullkey_to_info;
109+
const short_to_fullkey = context.short_to_fullkey;
110+
111+
const decompressed = {};
112+
113+
for ( let short in payload ) {
114+
if ( ! short_to_fullkey[short] ) {
115+
decompressed[short] = payload[short];
116+
continue;
117+
}
118+
119+
let k = short, v = payload[short];
120+
const fullkey = short_to_fullkey[short];
121+
const compress_info = fullkey_to_info[fullkey];
122+
123+
124+
if ( compress_info.short ) k = fullkey;
125+
if ( compress_info.values && compress_info.values.to_long[v] ) {
126+
v = compress_info.values.to_long[v];
127+
}
128+
129+
decompressed[k] = v;
130+
}
131+
132+
return decompressed;
133+
}
134+
135+
_test ({ assert }) {
136+
// Test compression
137+
{
138+
const context = this.compression.auth;
139+
const payload = {
140+
uuid: '123',
141+
type: 'session',
142+
user_uid: '456',
143+
app_uid: '789',
144+
};
145+
146+
const compressed = this._compress_payload(context, payload);
147+
assert(() => compressed.u === '123');
148+
assert(() => compressed.t === 's');
149+
assert(() => compressed.uu === '456');
150+
assert(() => compressed.au === '789');
151+
}
152+
153+
// Test decompression
154+
{
155+
const context = this.compression.auth;
156+
const payload = {
157+
u: '123',
158+
t: 's',
159+
uu: '456',
160+
au: '789',
161+
};
162+
163+
const decompressed = this._decompress_payload(context, payload);
164+
assert(() => decompressed.uuid === '123');
165+
assert(() => decompressed.type === 'session');
166+
assert(() => decompressed.user_uid === '456');
167+
assert(() => decompressed.app_uid === '789');
168+
}
169+
}
170+
}
171+
172+
module.exports = { TokenService };

packages/backend/tools/test.js

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const { AdvancedBase } = require("@heyputer/puter-js-common");
2+
const CoreModule = require("../src/CoreModule");
3+
const { Context } = require("../src/util/context");
4+
5+
class TestKernel extends AdvancedBase {
6+
constructor () {
7+
super();
8+
9+
this.modules = [];
10+
11+
this.logfn_ = (...a) => a;
12+
}
13+
14+
add_module (module) {
15+
this.modules.push(module);
16+
}
17+
18+
boot () {
19+
const { consoleLogManager } = require('../src/util/consolelog');
20+
consoleLogManager.initialize_proxy_methods();
21+
22+
consoleLogManager.decorate_all(({ manager, replace }, ...a) => {
23+
replace(...this.logfn_(...a));
24+
});
25+
26+
const { Container } = require('../src/services/Container');
27+
28+
const services = new Container();
29+
this.services = services;
30+
// app.set('services', services);
31+
32+
const root_context = Context.create({
33+
services,
34+
}, 'app');
35+
globalThis.root_context = root_context;
36+
37+
root_context.arun(async () => {
38+
await this._install_modules();
39+
// await this._boot_services();
40+
});
41+
42+
// Error.stackTraceLimit = Infinity;
43+
Error.stackTraceLimit = 200;
44+
}
45+
46+
async _install_modules () {
47+
const { services } = this;
48+
49+
for ( const module of this.modules ) {
50+
await module.install(Context.get());
51+
}
52+
53+
// Real kernel initializes services here, but in this test kernel
54+
// we don't initialize any services.
55+
56+
// Real kernel adds legacy services here but these will break
57+
// the test kernel.
58+
59+
services.ready.resolve();
60+
61+
// provide services to helpers
62+
// const { tmp_provide_services } = require('../src/helpers');
63+
// tmp_provide_services(services);
64+
}
65+
}
66+
67+
const k = new TestKernel();
68+
k.add_module(new CoreModule());
69+
k.boot();
70+
71+
const do_after_tests_ = [];
72+
73+
// const do_after_tests = (fn) => {
74+
// do_after_tests_.push(fn);
75+
// };
76+
const repeat_after = (fn) => {
77+
fn();
78+
do_after_tests_.push(fn);
79+
};
80+
81+
let total_passed = 0;
82+
let total_failed = 0;
83+
84+
for ( const name in k.services.instances_ ) {
85+
console.log('name', name)
86+
const ins = k.services.instances_[name];
87+
ins.construct();
88+
if ( ! ins._test || typeof ins._test !== 'function' ) {
89+
continue;
90+
}
91+
let passed = 0;
92+
let failed = 0;
93+
94+
repeat_after(() => {
95+
console.log(`\x1B[33;1m=== [ Service :: ${name} ] ===\x1B[0m`);
96+
});
97+
98+
const testapi = {
99+
assert: (condition, name) => {
100+
name = name || condition.toString();
101+
if ( condition() ) {
102+
passed++;
103+
repeat_after(() => console.log(`\x1B[32;1m ✔ ${name}\x1B[0m`));
104+
} else {
105+
failed++;
106+
repeat_after(() => console.log(`\x1B[31;1m ✘ ${name}\x1B[0m`));
107+
}
108+
}
109+
};
110+
111+
ins._test(testapi);
112+
113+
total_passed += passed;
114+
total_failed += failed;
115+
}
116+
117+
console.log(`\x1B[36;1m<===\x1B[0m ` +
118+
'ASSERTION OUTPUTS ARE REPEATED BELOW' +
119+
` \x1B[36;1m===>\x1B[0m`);
120+
121+
for ( const fn of do_after_tests_ ) {
122+
fn();
123+
}
124+
125+
console.log(`\x1B[36;1m=== [ Summary ] ===\x1B[0m`);
126+
console.log(`Passed: ${total_passed}`);
127+
console.log(`Failed: ${total_failed}`);

0 commit comments

Comments
 (0)