Skip to content

Commit 474c70f

Browse files
authored
Merge pull request #74 from NillionNetwork/fix/check-epochs-in-seconds
fix: add epoch timestamp parsing for token validity checks
2 parents 13ab032 + 3b04d76 commit 474c70f

File tree

5 files changed

+47
-12
lines changed

5 files changed

+47
-12
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nillion/nuc",
3-
"version": "0.1.0-rc.8",
3+
"version": "0.1.0-rc.9",
44
"license": "MIT",
55
"repository": "https://github.com/NillionNetwork/nuc-ts",
66
"engines": {

src/builder.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import {
1111
NucToken,
1212
NucTokenDataSchema,
1313
} from "#/token";
14-
import { base64UrlEncode, type Hex, randomBytes } from "#/utils";
14+
import {
15+
base64UrlEncode,
16+
type Hex,
17+
parseEpochSeconds,
18+
randomBytes,
19+
} from "#/utils";
1520

1621
const DEFAULT_NONCE_LENGTH = 16;
1722

@@ -102,9 +107,11 @@ export class NucTokenBuilder {
102107
/**
103108
* Set the token's `not before` instant.
104109
*
105-
* @param notBeforeInSeconds The Unix timestamp (in seconds) at which the token becomes valid.
110+
* @param epoch The Unix timestamp (in seconds) at which the token becomes valid.
111+
* @throws Error if the value is not a valid epoch timestamp.
106112
*/
107-
notBefore(notBeforeInSeconds: number): NucTokenBuilder {
113+
notBefore(epoch: number): NucTokenBuilder {
114+
const notBeforeInSeconds = parseEpochSeconds(epoch);
108115
this._notBefore = Temporal.Instant.fromEpochMilliseconds(
109116
notBeforeInSeconds * 1000,
110117
);
@@ -114,9 +121,11 @@ export class NucTokenBuilder {
114121
/**
115122
* Set the token's `expires at` instant.
116123
*
117-
* @param expiresAtInSeconds The Unix timestamp (in seconds) at which the token expires.
124+
* @param epoch The Unix timestamp (in seconds) at which the token expires.
125+
* @throws Error if the value is not a valid epoch timestamp.
118126
*/
119-
expiresAt(expiresAtInSeconds: number): NucTokenBuilder {
127+
expiresAt(epoch: number): NucTokenBuilder {
128+
const expiresAtInSeconds = parseEpochSeconds(epoch);
120129
this._expiresAt = Temporal.Instant.fromEpochMilliseconds(
121130
expiresAtInSeconds * 1000,
122131
);

src/nilauth/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Temporal } from "temporal-polyfill";
22
import z from "zod";
33
import { NucTokenEnvelopeSchema } from "#/envelope";
4+
import { EpochSeconds } from "#/utils";
45

56
const PUBLIC_KEY_LENGTH = 66;
67

@@ -59,8 +60,8 @@ export type SubscriptionCostResponse = z.output<
5960

6061
export const SubscriptionDetailsSchema = z
6162
.object({
62-
expires_at: z.number(),
63-
renewable_at: z.number(),
63+
expires_at: EpochSeconds,
64+
renewable_at: EpochSeconds,
6465
})
6566
.transform(({ expires_at, renewable_at }) => ({
6667
expiresAt: Temporal.Instant.fromEpochMilliseconds(expires_at * 1000),
@@ -84,7 +85,7 @@ export type CreateTokenResponse = z.infer<typeof CreateTokenResponseSchema>;
8485
export const RevokedTokenSchema = z
8586
.object({
8687
token_hash: z.string(),
87-
revoked_at: z.number(),
88+
revoked_at: EpochSeconds,
8889
})
8990
.transform(({ token_hash, revoked_at }) => ({
9091
tokenHash: token_hash,

src/token.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { dequal } from "dequal";
33
import { Temporal } from "temporal-polyfill";
44
import { z } from "zod";
55
import { type Policy, PolicySchema } from "#/policy";
6-
import { type Hex, HexSchema } from "#/utils";
6+
import { EpochSeconds, type Hex, HexSchema } from "#/utils";
77

88
const DID_EXPRESSION = /^did:nil:([a-zA-Z0-9]{66})$/;
99

@@ -123,8 +123,8 @@ export const NucTokenSchema = z
123123
iss: DidSchema,
124124
aud: DidSchema,
125125
sub: DidSchema,
126-
nbf: z.number().optional(),
127-
exp: z.number().optional(),
126+
nbf: EpochSeconds.optional(),
127+
exp: EpochSeconds.optional(),
128128
cmd: CommandSchema,
129129
args: InvocationBodySchema.optional(),
130130
pol: DelegationBodySchema.optional(),

src/utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ export const HexSchema = z.string().regex(/^[a-fA-F0-9]+$/, "invalid hex");
1313
/** Type for a validated hexadecimal string. */
1414
export type Hex = z.infer<typeof HexSchema>;
1515

16+
/**
17+
* Epoch timestamp in seconds.
18+
**/
19+
export const EpochSeconds = z.number().int().max(1e12);
20+
/**
21+
* Parses a value as an epoch timestamp in seconds.
22+
*
23+
* Validates that the value is an integer and does not exceed 1 trillion.
24+
* Throws a ZodError if validation fails.
25+
*
26+
* @param value - The value to parse as epoch seconds.
27+
* @returns The parsed epoch seconds as a number.
28+
* @throws Error if the value is not a valid epoch timestamp.
29+
* @example
30+
* parseEpochSeconds(1633036800) // Returns 1633036800
31+
* parseEpochSeconds("invalid") // Throws Error
32+
*/
33+
export const parseEpochSeconds = (value: unknown): number => {
34+
try {
35+
return EpochSeconds.parse(value);
36+
} catch (_e) {
37+
throw new Error(`Invalid epoch: ${value}`);
38+
}
39+
};
40+
1641
/**
1742
* Converts a UTF-8 string to hexadecimal.
1843
*

0 commit comments

Comments
 (0)