mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-24 15:07:17 +00:00
feat: add user created timestamp on whoami (#3538)
This commit is contained in:
@@ -109,6 +109,9 @@ describe('whoami extension — handleWhoami', () => {
|
||||
expect(body.oidc_only).toBe(false);
|
||||
// is_user_token is present (true) for user actors.
|
||||
expect(body.is_user_token).toBe(true);
|
||||
// Account creation time, in unix seconds.
|
||||
expect(typeof body.created_ts).toBe('number');
|
||||
expect(body.created_ts).toBeGreaterThan(0);
|
||||
// `directories` is only sent to user actors — confirm it’s present.
|
||||
expect(body.directories).toBeDefined();
|
||||
// taskbar_items is only sent to user actors.
|
||||
@@ -132,7 +135,7 @@ describe('whoami extension — handleWhoami', () => {
|
||||
expect(flags.payment_bypass).toBeUndefined();
|
||||
});
|
||||
|
||||
it('strips desktop_bg_* and human_readable_age fields for app actors', async () => {
|
||||
it('strips desktop_bg_*, created_ts and human_readable_age fields for app actors', async () => {
|
||||
const user = await seedUser();
|
||||
const { res, captured } = makeRes();
|
||||
|
||||
@@ -154,6 +157,8 @@ describe('whoami extension — handleWhoami', () => {
|
||||
expect(body.desktop_bg_color).toBeUndefined();
|
||||
expect(body.desktop_bg_fit).toBeUndefined();
|
||||
expect(body.human_readable_age).toBeUndefined();
|
||||
// Account age, in either form, is not exposed to apps.
|
||||
expect(body.created_ts).toBeUndefined();
|
||||
// Directories are user-only.
|
||||
expect(body.directories).toBeUndefined();
|
||||
});
|
||||
|
||||
+15
-10
@@ -14,6 +14,15 @@ const timeago = (() => {
|
||||
return new TimeAgo('en-US');
|
||||
})();
|
||||
|
||||
// User timestamps come off the DB as SQL datetime strings; the wire format
|
||||
// for all of them is unix seconds. Unparseable values are dropped rather
|
||||
// than sent as NaN.
|
||||
const toUnixSeconds = (value: unknown): number | undefined => {
|
||||
if (!value) return undefined;
|
||||
const ms = new Date(value as string | number | Date).getTime();
|
||||
return Number.isNaN(ms) ? undefined : Math.round(ms / 1000);
|
||||
};
|
||||
|
||||
// Allowlist of `config.feature_flags` keys safe to surface via /whoami.
|
||||
// Anything not listed here stays server-side, so internal flags
|
||||
// (payment_bypass, staff_only_*, etc.) cannot leak by accident. Add a
|
||||
@@ -98,6 +107,7 @@ export const handleWhoami = async (
|
||||
: undefined,
|
||||
otp: !!user.otp_enabled,
|
||||
feature_flags,
|
||||
created_ts: toUnixSeconds(user.timestamp),
|
||||
human_readable_age: user.timestamp
|
||||
? timeago.format(new Date(user.timestamp as string))
|
||||
: null,
|
||||
@@ -141,14 +151,9 @@ export const handleWhoami = async (
|
||||
}
|
||||
|
||||
// Last activity
|
||||
if (user.last_activity_ts) {
|
||||
try {
|
||||
details.last_activity_ts = Math.round(
|
||||
new Date(user.last_activity_ts as string).getTime() / 1000,
|
||||
);
|
||||
} catch {
|
||||
/* ignore parse error */
|
||||
}
|
||||
const lastActivityTs = toUnixSeconds(user.last_activity_ts);
|
||||
if (lastActivityTs !== undefined) {
|
||||
details.last_activity_ts = lastActivityTs;
|
||||
}
|
||||
|
||||
// Strip sensitive fields for app actors
|
||||
@@ -164,6 +169,7 @@ export const handleWhoami = async (
|
||||
delete details.desktop_bg_color;
|
||||
delete details.desktop_bg_fit;
|
||||
delete details.human_readable_age;
|
||||
delete details.created_ts;
|
||||
delete details.is_user_token;
|
||||
delete details.metadata;
|
||||
}
|
||||
@@ -183,8 +189,7 @@ export const handleWhoami = async (
|
||||
}
|
||||
|
||||
const subscription = details.subscription as
|
||||
| { offering?: Record<string, unknown> }
|
||||
| undefined;
|
||||
{ offering?: Record<string, unknown> } | undefined;
|
||||
if (subscription?.offering) {
|
||||
delete subscription.offering.group;
|
||||
delete subscription.offering.benefits;
|
||||
|
||||
@@ -27,6 +27,10 @@ A number value containing the user's free storage.
|
||||
|
||||
A string containing the current active app.
|
||||
|
||||
#### `created_ts` (Number)
|
||||
|
||||
A number value indicating when the user's account was created, in seconds since the Unix epoch. Only returned to user tokens; apps acting on a user's behalf do not receive it.
|
||||
|
||||
#### `is_temp` (Boolean)
|
||||
|
||||
A boolean value indicating whether the user's account is temporary.
|
||||
|
||||
@@ -5,6 +5,11 @@ export default suite('os', {
|
||||
const user = await t.puter.os.user();
|
||||
t.assert.ok(user && typeof user === 'object', 'user should be an object');
|
||||
t.assert.equal(user.username, t.env.users.user.username);
|
||||
t.assert.equal(
|
||||
typeof user.created_ts,
|
||||
'number',
|
||||
'created_ts should be unix seconds',
|
||||
);
|
||||
},
|
||||
|
||||
'user accepts trailing success/error callbacks': async (t) => {
|
||||
|
||||
Vendored
+5
@@ -12,6 +12,11 @@ export interface User {
|
||||
actual_free_storage?: number;
|
||||
/** The current active app. */
|
||||
app_name?: string;
|
||||
/**
|
||||
* When the account was created, in unix seconds. Only returned to user
|
||||
* tokens — apps acting on a user's behalf do not receive it.
|
||||
*/
|
||||
created_ts?: number;
|
||||
feature_flags?: Record<string, unknown>;
|
||||
hasDevAccountAccess?: boolean;
|
||||
/** Whether the user's account is temporary. */
|
||||
|
||||
Reference in New Issue
Block a user