@digiko-npm/cms 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,4 +17,33 @@ declare function verifyPassword(password: string, salt: string, storedHash: stri
17
17
  */
18
18
  declare function generateSessionToken(bytes?: number): string;
19
19
 
20
- export { generateSessionToken, hashPassword, verifyPassword };
20
+ /** Configuration for TOTP operations */
21
+ interface TotpConfig {
22
+ /** Hash algorithm. Default: 'SHA1' (required for 1Password compatibility) */
23
+ algorithm?: string;
24
+ /** Number of digits. Default: 6 */
25
+ digits?: number;
26
+ /** Time step in seconds. Default: 30 */
27
+ period?: number;
28
+ /** Validation window (number of periods to check before/after). Default: 1 */
29
+ window?: number;
30
+ }
31
+ /**
32
+ * Generate a new TOTP secret.
33
+ * Returns a base32-encoded string suitable for storing in env vars.
34
+ * Uses 20 bytes (160 bits) as recommended by RFC 4226.
35
+ */
36
+ declare function generateTotpSecret(): string;
37
+ /**
38
+ * Generate an otpauth:// URI for registering with authenticator apps.
39
+ * Can be entered manually in 1Password or encoded as a QR code.
40
+ */
41
+ declare function generateTotpUri(secret: string, accountName: string, issuer: string, config?: TotpConfig): string;
42
+ /**
43
+ * Verify a 6-digit TOTP code against a secret.
44
+ * Returns true if the code is valid within the configured time window.
45
+ * Uses timing-safe comparison internally (handled by otpauth library).
46
+ */
47
+ declare function verifyTotpCode(code: string, secret: string, config?: TotpConfig): boolean;
48
+
49
+ export { type TotpConfig, generateSessionToken, generateTotpSecret, generateTotpUri, hashPassword, verifyPassword, verifyTotpCode };
@@ -27,8 +27,45 @@ var DEFAULT_TOKEN_BYTES = 32;
27
27
  function generateSessionToken(bytes) {
28
28
  return crypto2.randomBytes(bytes ?? DEFAULT_TOKEN_BYTES).toString("hex");
29
29
  }
30
+
31
+ // src/auth/totp.ts
32
+ import { TOTP, Secret } from "otpauth";
33
+ var DEFAULTS2 = {
34
+ algorithm: "SHA1",
35
+ digits: 6,
36
+ period: 30,
37
+ window: 1
38
+ };
39
+ function generateTotpSecret() {
40
+ const secret = new Secret({ size: 20 });
41
+ return secret.base32;
42
+ }
43
+ function generateTotpUri(secret, accountName, issuer, config) {
44
+ const totp = new TOTP({
45
+ issuer,
46
+ label: accountName,
47
+ algorithm: config?.algorithm ?? DEFAULTS2.algorithm,
48
+ digits: config?.digits ?? DEFAULTS2.digits,
49
+ period: config?.period ?? DEFAULTS2.period,
50
+ secret: Secret.fromBase32(secret)
51
+ });
52
+ return totp.toString();
53
+ }
54
+ function verifyTotpCode(code, secret, config) {
55
+ const totp = new TOTP({
56
+ algorithm: config?.algorithm ?? DEFAULTS2.algorithm,
57
+ digits: config?.digits ?? DEFAULTS2.digits,
58
+ period: config?.period ?? DEFAULTS2.period,
59
+ secret: Secret.fromBase32(secret)
60
+ });
61
+ const delta = totp.validate({ token: code, window: config?.window ?? DEFAULTS2.window });
62
+ return delta !== null;
63
+ }
30
64
  export {
31
65
  generateSessionToken,
66
+ generateTotpSecret,
67
+ generateTotpUri,
32
68
  hashPassword,
33
- verifyPassword
69
+ verifyPassword,
70
+ verifyTotpCode
34
71
  };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createAdminClient, createBrowserClient, createPublicClient } from './supabase/index.js';
2
2
  export { UploadOptions, UploadResult, createR2Client, getR2Bucket, getR2PublicUrl, uploadFile } from './r2/index.js';
3
- export { generateSessionToken, hashPassword, verifyPassword } from './auth/index.js';
3
+ export { TotpConfig, generateSessionToken, generateTotpSecret, generateTotpUri, hashPassword, verifyPassword, verifyTotpCode } from './auth/index.js';
4
4
  export { RateLimiter, SessionStore, createRateLimiter, createSessionStore, getDefaultSessionDuration } from './session/index.js';
5
5
  export { HTTP_STATUS, HttpStatus } from './http/index.js';
6
6
  export { A as AuthConfig, R as R2Config, a as RateLimiterConfig, b as RequestVerifierConfig, S as SessionStoreConfig, c as SupabaseConfig, U as UploadConfig } from './config-qNdTlg1g.js';
package/dist/index.js CHANGED
@@ -123,6 +123,40 @@ function generateSessionToken(bytes) {
123
123
  return crypto2.randomBytes(bytes ?? DEFAULT_TOKEN_BYTES).toString("hex");
124
124
  }
125
125
 
126
+ // src/auth/totp.ts
127
+ import { TOTP, Secret } from "otpauth";
128
+ var DEFAULTS2 = {
129
+ algorithm: "SHA1",
130
+ digits: 6,
131
+ period: 30,
132
+ window: 1
133
+ };
134
+ function generateTotpSecret() {
135
+ const secret = new Secret({ size: 20 });
136
+ return secret.base32;
137
+ }
138
+ function generateTotpUri(secret, accountName, issuer, config) {
139
+ const totp = new TOTP({
140
+ issuer,
141
+ label: accountName,
142
+ algorithm: config?.algorithm ?? DEFAULTS2.algorithm,
143
+ digits: config?.digits ?? DEFAULTS2.digits,
144
+ period: config?.period ?? DEFAULTS2.period,
145
+ secret: Secret.fromBase32(secret)
146
+ });
147
+ return totp.toString();
148
+ }
149
+ function verifyTotpCode(code, secret, config) {
150
+ const totp = new TOTP({
151
+ algorithm: config?.algorithm ?? DEFAULTS2.algorithm,
152
+ digits: config?.digits ?? DEFAULTS2.digits,
153
+ period: config?.period ?? DEFAULTS2.period,
154
+ secret: Secret.fromBase32(secret)
155
+ });
156
+ const delta = totp.validate({ token: code, window: config?.window ?? DEFAULTS2.window });
157
+ return delta !== null;
158
+ }
159
+
126
160
  // src/session/store.ts
127
161
  import { Redis } from "@upstash/redis";
128
162
  var DEFAULT_SESSION_DURATION = 24 * 60 * 60 * 1e3;
@@ -160,7 +194,7 @@ function getDefaultSessionDuration() {
160
194
 
161
195
  // src/session/rate-limit.ts
162
196
  import { Redis as Redis2 } from "@upstash/redis";
163
- var DEFAULTS2 = {
197
+ var DEFAULTS3 = {
164
198
  maxAttempts: 10,
165
199
  windowMs: 15 * 60 * 1e3
166
200
  // 15 minutes
@@ -170,8 +204,8 @@ function createRateLimiter(config) {
170
204
  url: config.redisUrl,
171
205
  token: config.redisToken
172
206
  });
173
- const maxAttempts = config.maxAttempts ?? DEFAULTS2.maxAttempts;
174
- const windowMs = config.windowMs ?? DEFAULTS2.windowMs;
207
+ const maxAttempts = config.maxAttempts ?? DEFAULTS3.maxAttempts;
208
+ const windowMs = config.windowMs ?? DEFAULTS3.windowMs;
175
209
  const rateLimitKey = (key) => `${config.keyPrefix}ratelimit:${key}`;
176
210
  return {
177
211
  async check(key) {
@@ -224,10 +258,13 @@ export {
224
258
  createRateLimiter,
225
259
  createSessionStore,
226
260
  generateSessionToken,
261
+ generateTotpSecret,
262
+ generateTotpUri,
227
263
  getDefaultSessionDuration,
228
264
  getR2Bucket,
229
265
  getR2PublicUrl,
230
266
  hashPassword,
231
267
  uploadFile,
232
- verifyPassword
268
+ verifyPassword,
269
+ verifyTotpCode
233
270
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digiko-npm/cms",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Reusable CMS utilities — Supabase, Cloudflare R2, auth, sessions.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,36 +8,44 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
+ "types": "./dist/index.d.ts",
11
12
  "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
13
+ "default": "./dist/index.js"
13
14
  },
14
15
  "./supabase": {
16
+ "types": "./dist/supabase/index.d.ts",
15
17
  "import": "./dist/supabase/index.js",
16
- "types": "./dist/supabase/index.d.ts"
18
+ "default": "./dist/supabase/index.js"
17
19
  },
18
20
  "./r2": {
21
+ "types": "./dist/r2/index.d.ts",
19
22
  "import": "./dist/r2/index.js",
20
- "types": "./dist/r2/index.d.ts"
23
+ "default": "./dist/r2/index.js"
21
24
  },
22
25
  "./auth": {
26
+ "types": "./dist/auth/index.d.ts",
23
27
  "import": "./dist/auth/index.js",
24
- "types": "./dist/auth/index.d.ts"
28
+ "default": "./dist/auth/index.js"
25
29
  },
26
30
  "./session": {
31
+ "types": "./dist/session/index.d.ts",
27
32
  "import": "./dist/session/index.js",
28
- "types": "./dist/session/index.d.ts"
33
+ "default": "./dist/session/index.js"
29
34
  },
30
35
  "./next": {
36
+ "types": "./dist/next/index.d.ts",
31
37
  "import": "./dist/next/index.js",
32
- "types": "./dist/next/index.d.ts"
38
+ "default": "./dist/next/index.js"
33
39
  },
34
40
  "./http": {
41
+ "types": "./dist/http/index.d.ts",
35
42
  "import": "./dist/http/index.js",
36
- "types": "./dist/http/index.d.ts"
43
+ "default": "./dist/http/index.js"
37
44
  },
38
45
  "./types": {
46
+ "types": "./dist/types/index.d.ts",
39
47
  "import": "./dist/types/index.js",
40
- "types": "./dist/types/index.d.ts"
48
+ "default": "./dist/types/index.js"
41
49
  }
42
50
  },
43
51
  "files": [
@@ -56,9 +64,9 @@
56
64
  "access": "public"
57
65
  },
58
66
  "peerDependencies": {
59
- "@supabase/supabase-js": "^2.0.0",
60
67
  "@aws-sdk/client-s3": "^3.0.0",
61
68
  "@aws-sdk/s3-request-presigner": "^3.0.0",
69
+ "@supabase/supabase-js": "^2.0.0",
62
70
  "@upstash/redis": "^1.0.0",
63
71
  "next": ">=14.0.0"
64
72
  },
@@ -77,11 +85,11 @@
77
85
  }
78
86
  },
79
87
  "devDependencies": {
80
- "@supabase/supabase-js": "^2.95.0",
81
88
  "@aws-sdk/client-s3": "^3.986.0",
82
89
  "@aws-sdk/s3-request-presigner": "^3.986.0",
83
- "@upstash/redis": "^1.36.0",
90
+ "@supabase/supabase-js": "^2.95.0",
84
91
  "@types/node": "^20",
92
+ "@upstash/redis": "^1.36.0",
85
93
  "next": "16.1.6",
86
94
  "tsup": "^8.0.0",
87
95
  "typescript": "^5"
@@ -97,5 +105,8 @@
97
105
  "cloudflare-r2",
98
106
  "admin",
99
107
  "auth"
100
- ]
108
+ ],
109
+ "dependencies": {
110
+ "otpauth": "^9.5.0"
111
+ }
101
112
  }
package/src/auth/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { hashPassword, verifyPassword } from './password'
2
2
  export { generateSessionToken } from './token'
3
+ export { generateTotpSecret, generateTotpUri, verifyTotpCode, type TotpConfig } from './totp'
@@ -0,0 +1,72 @@
1
+ import { TOTP, Secret } from 'otpauth'
2
+
3
+ /** Configuration for TOTP operations */
4
+ export interface TotpConfig {
5
+ /** Hash algorithm. Default: 'SHA1' (required for 1Password compatibility) */
6
+ algorithm?: string
7
+ /** Number of digits. Default: 6 */
8
+ digits?: number
9
+ /** Time step in seconds. Default: 30 */
10
+ period?: number
11
+ /** Validation window (number of periods to check before/after). Default: 1 */
12
+ window?: number
13
+ }
14
+
15
+ const DEFAULTS = {
16
+ algorithm: 'SHA1',
17
+ digits: 6,
18
+ period: 30,
19
+ window: 1,
20
+ } as const
21
+
22
+ /**
23
+ * Generate a new TOTP secret.
24
+ * Returns a base32-encoded string suitable for storing in env vars.
25
+ * Uses 20 bytes (160 bits) as recommended by RFC 4226.
26
+ */
27
+ export function generateTotpSecret(): string {
28
+ const secret = new Secret({ size: 20 })
29
+ return secret.base32
30
+ }
31
+
32
+ /**
33
+ * Generate an otpauth:// URI for registering with authenticator apps.
34
+ * Can be entered manually in 1Password or encoded as a QR code.
35
+ */
36
+ export function generateTotpUri(
37
+ secret: string,
38
+ accountName: string,
39
+ issuer: string,
40
+ config?: TotpConfig
41
+ ): string {
42
+ const totp = new TOTP({
43
+ issuer,
44
+ label: accountName,
45
+ algorithm: config?.algorithm ?? DEFAULTS.algorithm,
46
+ digits: config?.digits ?? DEFAULTS.digits,
47
+ period: config?.period ?? DEFAULTS.period,
48
+ secret: Secret.fromBase32(secret),
49
+ })
50
+ return totp.toString()
51
+ }
52
+
53
+ /**
54
+ * Verify a 6-digit TOTP code against a secret.
55
+ * Returns true if the code is valid within the configured time window.
56
+ * Uses timing-safe comparison internally (handled by otpauth library).
57
+ */
58
+ export function verifyTotpCode(
59
+ code: string,
60
+ secret: string,
61
+ config?: TotpConfig
62
+ ): boolean {
63
+ const totp = new TOTP({
64
+ algorithm: config?.algorithm ?? DEFAULTS.algorithm,
65
+ digits: config?.digits ?? DEFAULTS.digits,
66
+ period: config?.period ?? DEFAULTS.period,
67
+ secret: Secret.fromBase32(secret),
68
+ })
69
+
70
+ const delta = totp.validate({ token: code, window: config?.window ?? DEFAULTS.window })
71
+ return delta !== null
72
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export { uploadFile, type UploadOptions, type UploadResult } from './r2/upload'
9
9
  // Auth
10
10
  export { hashPassword, verifyPassword } from './auth/password'
11
11
  export { generateSessionToken } from './auth/token'
12
+ export { generateTotpSecret, generateTotpUri, verifyTotpCode, type TotpConfig } from './auth/totp'
12
13
 
13
14
  // Sessions
14
15
  export { createSessionStore, getDefaultSessionDuration, type SessionStore } from './session/store'