@obfious/js 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.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @obfious/js
2
+
3
+ Device identity protection for JavaScript applications.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @obfious/js
9
+ ```
10
+
11
+ ## Platforms
12
+
13
+ | Import | Platform |
14
+ |--------|----------|
15
+ | `@obfious/js` | Cloudflare Workers, Deno, Bun, any Web API runtime |
16
+ | `@obfious/js/nextjs` | Next.js (App Router middleware) |
17
+ | `@obfious/js/express` | Express / Connect |
18
+ | `@obfious/js/fastify` | Fastify |
19
+ | `@obfious/js/lambda` | AWS Lambda (API Gateway) |
20
+
21
+ ## Quick Start
22
+
23
+ ```typescript
24
+ import { Obfious } from "@obfious/js";
25
+
26
+ const obfious = new Obfious({
27
+ includePaths: ["/api"],
28
+ });
29
+
30
+ // In your request handler:
31
+ const result = await obfious.protect(request, {
32
+ keyId: process.env.OBFIOUS_KEY_ID,
33
+ secret: process.env.OBFIOUS_SECRET,
34
+ });
35
+ if (result.response) return result.response;
36
+
37
+ // Inject the client script in your HTML <head>:
38
+ // <script src="${obfious.clientScript}" nonce="${obfious.cspNonce}" defer></script>
39
+ ```
40
+
41
+ ### Next.js
42
+
43
+ ```typescript
44
+ import { createObfiousMiddleware } from "@obfious/js/nextjs";
45
+ import { NextResponse } from "next/server";
46
+
47
+ const obfious = createObfiousMiddleware({
48
+ creds: { keyId: process.env.OBFIOUS_KEY_ID!, secret: process.env.OBFIOUS_SECRET! },
49
+ includePaths: ["/api/"],
50
+ });
51
+
52
+ export async function middleware(request) {
53
+ const response = await obfious(request);
54
+ if (response) return response;
55
+ return NextResponse.next();
56
+ }
57
+ ```
58
+
59
+ ### Express
60
+
61
+ ```typescript
62
+ import express from "express";
63
+ import { obfiousMiddleware } from "@obfious/js/express";
64
+
65
+ const app = express();
66
+ app.use(obfiousMiddleware({
67
+ creds: { keyId: process.env.OBFIOUS_KEY_ID!, secret: process.env.OBFIOUS_SECRET! },
68
+ includePaths: ["/api/"],
69
+ }));
70
+ ```
71
+
72
+ ## License
73
+
74
+ See LICENSE file.
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 } from "@obfious/js";
3
+ export { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 };
4
+ export interface ObfiousExpressOptionsV2 extends ObfiousConfigV2 {
5
+ creds: ObfiousCredsV2;
6
+ getUser?: (req: IncomingMessage) => string | undefined;
7
+ }
8
+ export declare function obfiousMiddleware(options: ObfiousExpressOptionsV2): (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => Promise<void>;
@@ -0,0 +1 @@
1
+ var b="x-obfious-key",P="x-obfious-sig",v="x-obfious-ts",R=/\.(json|js|gif|png|woff2|css)$/,l=class{config;creds=null;scriptPathCache=null;constructor(e={}){this.config={...e,apiUrl:e.apiUrl??"https://api.obfious.com"}}async getScriptPath(){if(this.config.scriptPath)return this.config.scriptPath;if(this.scriptPathCache)return this.scriptPathCache;let e=this.config.stableString||"obfious-default";return this.scriptPathCache=await C(e),this.scriptPathCache}async scriptTag(e){let t=await this.getScriptPath(),s=e?.nonce?` nonce="${e.nonce}"`:"";return`<script src="${t}"${s} defer></script>`}async protect(e,t,s){let r={response:null};if(t&&!this.creds&&(this.creds=t),!this.creds)return r;let n=new URL(e.url);if(e.method==="GET"){let p=await this.getScriptPath();if(n.pathname===p){let u=await this.fetchBundle();if(u)return{response:new Response(u,{headers:{"Content-Type":"application/javascript","Cache-Control":"no-store"}})}}}if(e.method==="POST"&&R.test(n.pathname)){let p=e.clone(),u=new Uint8Array(await p.arrayBuffer());if(u.length>0&&u[0]===91)return{response:await this.forwardToApi(n.pathname,u)}}if(this.config.excludePaths?.some(p=>n.pathname.startsWith(p))||this.config.includePaths&&!this.config.includePaths.some(p=>n.pathname.startsWith(p)))return r;let a=e.headers.get("x-req-auth");if(!a)return{response:new Response(null,{status:401})};let o=a.indexOf(".");if(o<1)return{response:new Response(null,{status:401})};let c=a.slice(0,o),h=a.slice(o+1),d=A(c);if(!d)return{response:new Response(null,{status:401})};let w=s&&this.config.privateKey?await S(s,this.config.privateKey):void 0,f=await this.validateToken(d,c,h,w);return f.valid?{response:null,deviceId:f.deviceId}:{response:new Response(null,{status:401})}}getIp(e){return this.config.getClientIp?this.config.getClientIp(e):e.headers.get("CF-Connecting-IP")||e.headers.get("X-Forwarded-For")?.split(",")[0]?.trim()||e.headers.get("X-Real-IP")||"unknown"}async fetchBundle(){try{let e=await this.authedFetch("/b",{method:"GET"});return e.ok?await e.text():null}catch{return null}}async forwardToApi(e,t){return this.authedFetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:t.buffer})}async validateToken(e,t,s,r){try{let n={tokenHex:e,signature:s,payload:t};r&&(n.encryptedUser=r);let a=await this.authedFetch("/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!a.ok)return{valid:!1};let o=await a.json();return{valid:o.valid===!0,deviceId:o.deviceId}}catch{return{valid:!1}}}async authedFetch(e,t){let s=`${this.config.apiUrl}${e}`,r=Date.now().toString(),n=(t.method||"GET").toUpperCase(),a=`${r}.${n}.${e}`,o=await x(this.creds.secret,a),c=new Headers(t.headers);return c.set(b,this.creds.keyId),c.set(P,o),c.set(v,r),fetch(s,{...t,headers:c})}};async function C(i){let e=await crypto.subtle.importKey("raw",new TextEncoder().encode("obfious-script-v1"),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),t=await crypto.subtle.sign("HMAC",e,new TextEncoder().encode(i));return`/${Array.from(new Uint8Array(t),r=>r.toString(16).padStart(2,"0")).join("").slice(0,10)}.js`}async function x(i,e){let t=new TextEncoder().encode(i),s=await crypto.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),r=await crypto.subtle.sign("HMAC",s,new TextEncoder().encode(e));return Array.from(new Uint8Array(r),n=>n.toString(16).padStart(2,"0")).join("")}async function S(i,e){let t=new TextEncoder().encode(e),s=await crypto.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),r=await crypto.subtle.sign("HMAC",s,new TextEncoder().encode(i));return Array.from(new Uint8Array(r),n=>n.toString(16).padStart(2,"0")).join("")}function A(i){try{let e=i.replace(/-/g,"+").replace(/_/g,"/");for(;e.length%4;)e+="=";let t=Uint8Array.from(atob(e),s=>s.charCodeAt(0));return t.length<9||t[0]!==33?null:Array.from(t.slice(1,9),s=>s.toString(16).padStart(2,"0")).join("")}catch{return null}}import{Readable as g}from"node:stream";function y(i){let e=i.headers["x-forwarded-proto"]||"http",t=i.headers.host||"localhost",s=`${e}://${t}${i.url}`,r=new Headers;for(let[a,o]of Object.entries(i.headers))o&&r.set(a,Array.isArray(o)?o.join(", "):o);let n=i.method!=="GET"&&i.method!=="HEAD";return new Request(s,{method:i.method,headers:r,body:n?g.toWeb(g.from(i)):null,duplex:"half"})}async function m(i,e){let t={};if(e.headers.forEach((s,r)=>{t[r]=s}),i.writeHead(e.status,t),e.body){let s=e.body.getReader();try{for(;;){let{done:r,value:n}=await s.read();if(r)break;i.write(n)}}finally{s.releaseLock()}}i.end()}function k(i){let{creds:e,getUser:t,...s}=i,r=new l({...s,getClientIp:s.getClientIp??(n=>n.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||n.headers.get("x-real-ip")||"unknown"),getPlatformSignals:s.getPlatformSignals??(()=>({}))});return async(n,a,o)=>{try{let c=y(n),h=t?.(n),d=await r.protect(c,e,h);if(d.response){await m(a,d.response);return}d.deviceId&&(n.obfiousDeviceId=d.deviceId),o()}catch(c){o(c)}}}export{l as ObfiousV2,k as obfiousMiddleware};
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 } from "@obfious/js";
3
+ export { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 };
4
+ export interface ObfiousFastifyOptionsV2 extends ObfiousConfigV2 {
5
+ creds: ObfiousCredsV2;
6
+ getUser?: (req: IncomingMessage) => string | undefined;
7
+ }
8
+ export declare function obfiousPlugin(fastify: any, options: ObfiousFastifyOptionsV2): Promise<void>;
@@ -0,0 +1 @@
1
+ var w="x-obfious-key",b="x-obfious-sig",P="x-obfious-ts",R=/\.(json|js|gif|png|woff2|css)$/,f=class{config;creds=null;scriptPathCache=null;constructor(t={}){this.config={...t,apiUrl:t.apiUrl??"https://api.obfious.com"}}async getScriptPath(){if(this.config.scriptPath)return this.config.scriptPath;if(this.scriptPathCache)return this.scriptPathCache;let t=this.config.stableString||"obfious-default";return this.scriptPathCache=await v(t),this.scriptPathCache}async scriptTag(t){let e=await this.getScriptPath(),n=t?.nonce?` nonce="${t.nonce}"`:"";return`<script src="${e}"${n} defer></script>`}async protect(t,e,n){let s={response:null};if(e&&!this.creds&&(this.creds=e),!this.creds)return s;let i=new URL(t.url);if(t.method==="GET"){let u=await this.getScriptPath();if(i.pathname===u){let p=await this.fetchBundle();if(p)return{response:new Response(p,{headers:{"Content-Type":"application/javascript","Cache-Control":"no-store"}})}}}if(t.method==="POST"&&R.test(i.pathname)){let u=t.clone(),p=new Uint8Array(await u.arrayBuffer());if(p.length>0&&p[0]===91)return{response:await this.forwardToApi(i.pathname,p)}}if(this.config.excludePaths?.some(u=>i.pathname.startsWith(u))||this.config.includePaths&&!this.config.includePaths.some(u=>i.pathname.startsWith(u)))return s;let o=t.headers.get("x-req-auth");if(!o)return{response:new Response(null,{status:401})};let a=o.indexOf(".");if(a<1)return{response:new Response(null,{status:401})};let c=o.slice(0,a),g=o.slice(a+1),d=S(c);if(!d)return{response:new Response(null,{status:401})};let h=n&&this.config.privateKey?await x(n,this.config.privateKey):void 0,l=await this.validateToken(d,c,g,h);return l.valid?{response:null,deviceId:l.deviceId}:{response:new Response(null,{status:401})}}getIp(t){return this.config.getClientIp?this.config.getClientIp(t):t.headers.get("CF-Connecting-IP")||t.headers.get("X-Forwarded-For")?.split(",")[0]?.trim()||t.headers.get("X-Real-IP")||"unknown"}async fetchBundle(){try{let t=await this.authedFetch("/b",{method:"GET"});return t.ok?await t.text():null}catch{return null}}async forwardToApi(t,e){return this.authedFetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:e.buffer})}async validateToken(t,e,n,s){try{let i={tokenHex:t,signature:n,payload:e};s&&(i.encryptedUser=s);let o=await this.authedFetch("/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!o.ok)return{valid:!1};let a=await o.json();return{valid:a.valid===!0,deviceId:a.deviceId}}catch{return{valid:!1}}}async authedFetch(t,e){let n=`${this.config.apiUrl}${t}`,s=Date.now().toString(),i=(e.method||"GET").toUpperCase(),o=`${s}.${i}.${t}`,a=await C(this.creds.secret,o),c=new Headers(e.headers);return c.set(w,this.creds.keyId),c.set(b,a),c.set(P,s),fetch(n,{...e,headers:c})}};async function v(r){let t=await crypto.subtle.importKey("raw",new TextEncoder().encode("obfious-script-v1"),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),e=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(r));return`/${Array.from(new Uint8Array(e),s=>s.toString(16).padStart(2,"0")).join("").slice(0,10)}.js`}async function C(r,t){let e=new TextEncoder().encode(r),n=await crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",n,new TextEncoder().encode(t));return Array.from(new Uint8Array(s),i=>i.toString(16).padStart(2,"0")).join("")}async function x(r,t){let e=new TextEncoder().encode(t),n=await crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",n,new TextEncoder().encode(r));return Array.from(new Uint8Array(s),i=>i.toString(16).padStart(2,"0")).join("")}function S(r){try{let t=r.replace(/-/g,"+").replace(/_/g,"/");for(;t.length%4;)t+="=";let e=Uint8Array.from(atob(t),n=>n.charCodeAt(0));return e.length<9||e[0]!==33?null:Array.from(e.slice(1,9),n=>n.toString(16).padStart(2,"0")).join("")}catch{return null}}import{Readable as y}from"node:stream";function m(r){let t=r.headers["x-forwarded-proto"]||"http",e=r.headers.host||"localhost",n=`${t}://${e}${r.url}`,s=new Headers;for(let[o,a]of Object.entries(r.headers))a&&s.set(o,Array.isArray(a)?a.join(", "):a);let i=r.method!=="GET"&&r.method!=="HEAD";return new Request(n,{method:r.method,headers:s,body:i?y.toWeb(y.from(r)):null,duplex:"half"})}async function V(r,t){let{creds:e,getUser:n,...s}=t,i=new f({...s,getClientIp:s.getClientIp??(o=>o.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||o.headers.get("x-real-ip")||"unknown"),getPlatformSignals:s.getPlatformSignals??(()=>({}))});r.addHook("onRequest",async(o,a)=>{let c=m(o.raw),g=n?.(o.raw),d=await i.protect(c,e,g);if(d.response){let h={};d.response.headers.forEach((u,p)=>{h[p]=u});let l=await d.response.text();a.code(d.response.status).headers(h).send(l);return}d.deviceId&&(o.obfiousDeviceId=d.deviceId)})}export{f as ObfiousV2,V as obfiousPlugin};
@@ -0,0 +1,24 @@
1
+ export interface ObfiousConfigV2 {
2
+ apiUrl?: string;
3
+ stableString?: string;
4
+ scriptPath?: string;
5
+ includePaths?: string[];
6
+ excludePaths?: string[];
7
+ privateKey?: string;
8
+ getClientIp?: (request: Request) => string;
9
+ getPlatformSignals?: (request: Request) => Record<string, string>;
10
+ }
11
+ export interface ObfiousCredsV2 {
12
+ keyId: string;
13
+ secret: string;
14
+ }
15
+ export interface ProtectResultV2 {
16
+ response: Response | null;
17
+ deviceId?: string;
18
+ }
19
+ export declare class ObfiousV2 {
20
+ constructor(config?: ObfiousConfigV2);
21
+ getScriptPath(): Promise<string>;
22
+ scriptTag(opts?: { nonce?: string }): Promise<string>;
23
+ protect(request: Request, creds?: ObfiousCredsV2, user?: string): Promise<ProtectResultV2>;
24
+ }
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var y="x-obfious-key",w="x-obfious-sig",m="x-obfious-ts",P=/\.(json|js|gif|png|woff2|css)$/,u=class{config;creds=null;scriptPathCache=null;constructor(t={}){this.config={...t,apiUrl:t.apiUrl??"https://api.obfious.com"}}async getScriptPath(){if(this.config.scriptPath)return this.config.scriptPath;if(this.scriptPathCache)return this.scriptPathCache;let t=this.config.stableString||"obfious-default";return this.scriptPathCache=await b(t),this.scriptPathCache}async scriptTag(t){let e=await this.getScriptPath(),n=t?.nonce?` nonce="${t.nonce}"`:"";return`<script src="${e}"${n} defer></script>`}async protect(t,e,n){let s={response:null};if(e&&!this.creds&&(this.creds=e),!this.creds)return s;let r=new URL(t.url);if(t.method==="GET"){let h=await this.getScriptPath();if(r.pathname===h){let l=await this.fetchBundle();if(l)return{response:new Response(l,{headers:{"Content-Type":"application/javascript","Cache-Control":"no-store"}})}}}if(t.method==="POST"&&P.test(r.pathname)){let h=t.clone(),l=new Uint8Array(await h.arrayBuffer());if(l.length>0&&l[0]===91)return{response:await this.forwardToApi(r.pathname,l)}}if(this.config.excludePaths?.some(h=>r.pathname.startsWith(h))||this.config.includePaths&&!this.config.includePaths.some(h=>r.pathname.startsWith(h)))return s;let i=t.headers.get("x-req-auth");if(!i)return{response:new Response(null,{status:401})};let o=i.indexOf(".");if(o<1)return{response:new Response(null,{status:401})};let c=i.slice(0,o),g=i.slice(o+1),p=x(c);if(!p)return{response:new Response(null,{status:401})};let f=n&&this.config.privateKey?await v(n,this.config.privateKey):void 0,d=await this.validateToken(p,c,g,f);return d.valid?{response:null,deviceId:d.deviceId}:{response:new Response(null,{status:401})}}getIp(t){return this.config.getClientIp?this.config.getClientIp(t):t.headers.get("CF-Connecting-IP")||t.headers.get("X-Forwarded-For")?.split(",")[0]?.trim()||t.headers.get("X-Real-IP")||"unknown"}async fetchBundle(){try{let t=await this.authedFetch("/b",{method:"GET"});return t.ok?await t.text():null}catch{return null}}async forwardToApi(t,e){return this.authedFetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:e.buffer})}async validateToken(t,e,n,s){try{let r={tokenHex:t,signature:n,payload:e};s&&(r.encryptedUser=s);let i=await this.authedFetch("/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!i.ok)return{valid:!1};let o=await i.json();return{valid:o.valid===!0,deviceId:o.deviceId}}catch{return{valid:!1}}}async authedFetch(t,e){let n=`${this.config.apiUrl}${t}`,s=Date.now().toString(),r=(e.method||"GET").toUpperCase(),i=`${s}.${r}.${t}`,o=await C(this.creds.secret,i),c=new Headers(e.headers);return c.set(y,this.creds.keyId),c.set(w,o),c.set(m,s),fetch(n,{...e,headers:c})}};async function b(a){let t=await crypto.subtle.importKey("raw",new TextEncoder().encode("obfious-script-v1"),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),e=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(a));return`/${Array.from(new Uint8Array(e),s=>s.toString(16).padStart(2,"0")).join("").slice(0,10)}.js`}async function C(a,t){let e=new TextEncoder().encode(a),n=await crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",n,new TextEncoder().encode(t));return Array.from(new Uint8Array(s),r=>r.toString(16).padStart(2,"0")).join("")}async function v(a,t){let e=new TextEncoder().encode(t),n=await crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",n,new TextEncoder().encode(a));return Array.from(new Uint8Array(s),r=>r.toString(16).padStart(2,"0")).join("")}function x(a){try{let t=a.replace(/-/g,"+").replace(/_/g,"/");for(;t.length%4;)t+="=";let e=Uint8Array.from(atob(t),n=>n.charCodeAt(0));return e.length<9||e[0]!==33?null:Array.from(e.slice(1,9),n=>n.toString(16).padStart(2,"0")).join("")}catch{return null}}export{u as ObfiousV2};
@@ -0,0 +1,24 @@
1
+ import { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 } from "@obfious/js";
2
+ export { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 };
3
+ export interface APIGatewayProxyEvent {
4
+ httpMethod: string;
5
+ path: string;
6
+ headers: Record<string, string | undefined>;
7
+ multiValueHeaders?: Record<string, string[] | undefined>;
8
+ queryStringParameters?: Record<string, string | undefined> | null;
9
+ body: string | null;
10
+ isBase64Encoded: boolean;
11
+ requestContext: { identity?: { sourceIp?: string }; [key: string]: any };
12
+ }
13
+ export interface APIGatewayProxyResult {
14
+ statusCode: number;
15
+ headers?: Record<string, string>;
16
+ body: string;
17
+ isBase64Encoded?: boolean;
18
+ }
19
+ export type LambdaHandler = (event: APIGatewayProxyEvent, context: any) => Promise<APIGatewayProxyResult>;
20
+ export interface ObfiousLambdaOptionsV2 extends ObfiousConfigV2 {
21
+ creds: ObfiousCredsV2;
22
+ getUser?: (event: APIGatewayProxyEvent) => string | undefined;
23
+ }
24
+ export declare function obfiousHandler(options: ObfiousLambdaOptionsV2, handler: LambdaHandler): LambdaHandler;
package/dist/lambda.js ADDED
@@ -0,0 +1 @@
1
+ var y="x-obfious-key",m="x-obfious-sig",w="x-obfious-ts",b=/\.(json|js|gif|png|woff2|css)$/,g=class{config;creds=null;scriptPathCache=null;constructor(t={}){this.config={...t,apiUrl:t.apiUrl??"https://api.obfious.com"}}async getScriptPath(){if(this.config.scriptPath)return this.config.scriptPath;if(this.scriptPathCache)return this.scriptPathCache;let t=this.config.stableString||"obfious-default";return this.scriptPathCache=await P(t),this.scriptPathCache}async scriptTag(t){let s=await this.getScriptPath(),r=t?.nonce?` nonce="${t.nonce}"`:"";return`<script src="${s}"${r} defer></script>`}async protect(t,s,r){let n={response:null};if(s&&!this.creds&&(this.creds=s),!this.creds)return n;let o=new URL(t.url);if(t.method==="GET"){let u=await this.getScriptPath();if(o.pathname===u){let h=await this.fetchBundle();if(h)return{response:new Response(h,{headers:{"Content-Type":"application/javascript","Cache-Control":"no-store"}})}}}if(t.method==="POST"&&b.test(o.pathname)){let u=t.clone(),h=new Uint8Array(await u.arrayBuffer());if(h.length>0&&h[0]===91)return{response:await this.forwardToApi(o.pathname,h)}}if(this.config.excludePaths?.some(u=>o.pathname.startsWith(u))||this.config.includePaths&&!this.config.includePaths.some(u=>o.pathname.startsWith(u)))return n;let i=t.headers.get("x-req-auth");if(!i)return{response:new Response(null,{status:401})};let c=i.indexOf(".");if(c<1)return{response:new Response(null,{status:401})};let a=i.slice(0,c),p=i.slice(c+1),d=R(a);if(!d)return{response:new Response(null,{status:401})};let l=r&&this.config.privateKey?await C(r,this.config.privateKey):void 0,f=await this.validateToken(d,a,p,l);return f.valid?{response:null,deviceId:f.deviceId}:{response:new Response(null,{status:401})}}getIp(t){return this.config.getClientIp?this.config.getClientIp(t):t.headers.get("CF-Connecting-IP")||t.headers.get("X-Forwarded-For")?.split(",")[0]?.trim()||t.headers.get("X-Real-IP")||"unknown"}async fetchBundle(){try{let t=await this.authedFetch("/b",{method:"GET"});return t.ok?await t.text():null}catch{return null}}async forwardToApi(t,s){return this.authedFetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:s.buffer})}async validateToken(t,s,r,n){try{let o={tokenHex:t,signature:r,payload:s};n&&(o.encryptedUser=n);let i=await this.authedFetch("/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});if(!i.ok)return{valid:!1};let c=await i.json();return{valid:c.valid===!0,deviceId:c.deviceId}}catch{return{valid:!1}}}async authedFetch(t,s){let r=`${this.config.apiUrl}${t}`,n=Date.now().toString(),o=(s.method||"GET").toUpperCase(),i=`${n}.${o}.${t}`,c=await x(this.creds.secret,i),a=new Headers(s.headers);return a.set(y,this.creds.keyId),a.set(m,c),a.set(w,n),fetch(r,{...s,headers:a})}};async function P(e){let t=await crypto.subtle.importKey("raw",new TextEncoder().encode("obfious-script-v1"),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(e));return`/${Array.from(new Uint8Array(s),n=>n.toString(16).padStart(2,"0")).join("").slice(0,10)}.js`}async function x(e,t){let s=new TextEncoder().encode(e),r=await crypto.subtle.importKey("raw",s,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),n=await crypto.subtle.sign("HMAC",r,new TextEncoder().encode(t));return Array.from(new Uint8Array(n),o=>o.toString(16).padStart(2,"0")).join("")}async function C(e,t){let s=new TextEncoder().encode(t),r=await crypto.subtle.importKey("raw",s,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),n=await crypto.subtle.sign("HMAC",r,new TextEncoder().encode(e));return Array.from(new Uint8Array(n),o=>o.toString(16).padStart(2,"0")).join("")}function R(e){try{let t=e.replace(/-/g,"+").replace(/_/g,"/");for(;t.length%4;)t+="=";let s=Uint8Array.from(atob(t),r=>r.charCodeAt(0));return s.length<9||s[0]!==33?null:Array.from(s.slice(1,9),r=>r.toString(16).padStart(2,"0")).join("")}catch{return null}}function S(e){let t=e.headers["x-forwarded-proto"]||e.headers["X-Forwarded-Proto"]||"https",s=e.headers.host||e.headers.Host||"localhost",r=`${t}://${s}${e.path}`;if(e.queryStringParameters){let c=new URLSearchParams;for(let[p,d]of Object.entries(e.queryStringParameters))d!=null&&c.set(p,d);let a=c.toString();a&&(r+=`?${a}`)}let n=new Headers;for(let[c,a]of Object.entries(e.headers))a&&n.set(c,a);let i=e.httpMethod!=="GET"&&e.httpMethod!=="HEAD"&&e.body!=null?e.isBase64Encoded?atob(e.body):e.body:null;return new Request(r,{method:e.httpMethod,headers:n,body:i})}async function A(e){let t={};return e.headers.forEach((s,r)=>{t[r]=s}),{statusCode:e.status,headers:t,body:await e.text()}}function H(e,t){let{creds:s,getUser:r,...n}=e,o=new g({...n,getClientIp:n.getClientIp??(i=>i.headers.get("x-lambda-source-ip")||i.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown"),getPlatformSignals:n.getPlatformSignals??(()=>({}))});return async(i,c)=>{let a=S(i),p=i.requestContext?.identity?.sourceIp||i.headers["x-forwarded-for"]?.split(",")[0]?.trim()||"unknown";a.headers.set("x-lambda-source-ip",p);let d=r?.(i),l=await o.protect(a,s,d);return l.response?A(l.response):(l.deviceId&&(i.headers["x-obfious-device-id"]=String(l.deviceId)),t(i,c))}}export{g as ObfiousV2,H as obfiousHandler};
@@ -0,0 +1,7 @@
1
+ import { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 } from "@obfious/js";
2
+ export { ObfiousV2, ObfiousConfigV2, ObfiousCredsV2, ProtectResultV2 };
3
+ export interface ObfiousMiddlewareConfigV2 extends ObfiousConfigV2 {
4
+ creds: ObfiousCredsV2;
5
+ }
6
+ export declare function createObfiousMiddleware(config: ObfiousMiddlewareConfigV2): (request: Request) => Promise<Response | null>;
7
+ export declare function obfiousScriptTag(obfious: ObfiousV2, nonce?: string): Promise<string>;
package/dist/nextjs.js ADDED
@@ -0,0 +1 @@
1
+ var y="x-obfious-key",w="x-obfious-sig",m="x-obfious-ts",b=/\.(json|js|gif|png|woff2|css)$/,p=class{config;creds=null;scriptPathCache=null;constructor(t={}){this.config={...t,apiUrl:t.apiUrl??"https://api.obfious.com"}}async getScriptPath(){if(this.config.scriptPath)return this.config.scriptPath;if(this.scriptPathCache)return this.scriptPathCache;let t=this.config.stableString||"obfious-default";return this.scriptPathCache=await C(t),this.scriptPathCache}async scriptTag(t){let e=await this.getScriptPath(),n=t?.nonce?` nonce="${t.nonce}"`:"";return`<script src="${e}"${n} defer></script>`}async protect(t,e,n){let s={response:null};if(e&&!this.creds&&(this.creds=e),!this.creds)return s;let r=new URL(t.url);if(t.method==="GET"){let u=await this.getScriptPath();if(r.pathname===u){let l=await this.fetchBundle();if(l)return{response:new Response(l,{headers:{"Content-Type":"application/javascript","Cache-Control":"no-store"}})}}}if(t.method==="POST"&&b.test(r.pathname)){let u=t.clone(),l=new Uint8Array(await u.arrayBuffer());if(l.length>0&&l[0]===91)return{response:await this.forwardToApi(r.pathname,l)}}if(this.config.excludePaths?.some(u=>r.pathname.startsWith(u))||this.config.includePaths&&!this.config.includePaths.some(u=>r.pathname.startsWith(u)))return s;let o=t.headers.get("x-req-auth");if(!o)return{response:new Response(null,{status:401})};let a=o.indexOf(".");if(a<1)return{response:new Response(null,{status:401})};let c=o.slice(0,a),h=o.slice(a+1),d=v(c);if(!d)return{response:new Response(null,{status:401})};let g=n&&this.config.privateKey?await x(n,this.config.privateKey):void 0,f=await this.validateToken(d,c,h,g);return f.valid?{response:null,deviceId:f.deviceId}:{response:new Response(null,{status:401})}}getIp(t){return this.config.getClientIp?this.config.getClientIp(t):t.headers.get("CF-Connecting-IP")||t.headers.get("X-Forwarded-For")?.split(",")[0]?.trim()||t.headers.get("X-Real-IP")||"unknown"}async fetchBundle(){try{let t=await this.authedFetch("/b",{method:"GET"});return t.ok?await t.text():null}catch{return null}}async forwardToApi(t,e){return this.authedFetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:e.buffer})}async validateToken(t,e,n,s){try{let r={tokenHex:t,signature:n,payload:e};s&&(r.encryptedUser=s);let o=await this.authedFetch("/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!o.ok)return{valid:!1};let a=await o.json();return{valid:a.valid===!0,deviceId:a.deviceId}}catch{return{valid:!1}}}async authedFetch(t,e){let n=`${this.config.apiUrl}${t}`,s=Date.now().toString(),r=(e.method||"GET").toUpperCase(),o=`${s}.${r}.${t}`,a=await P(this.creds.secret,o),c=new Headers(e.headers);return c.set(y,this.creds.keyId),c.set(w,a),c.set(m,s),fetch(n,{...e,headers:c})}};async function C(i){let t=await crypto.subtle.importKey("raw",new TextEncoder().encode("obfious-script-v1"),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),e=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(i));return`/${Array.from(new Uint8Array(e),s=>s.toString(16).padStart(2,"0")).join("").slice(0,10)}.js`}async function P(i,t){let e=new TextEncoder().encode(i),n=await crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",n,new TextEncoder().encode(t));return Array.from(new Uint8Array(s),r=>r.toString(16).padStart(2,"0")).join("")}async function x(i,t){let e=new TextEncoder().encode(t),n=await crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",n,new TextEncoder().encode(i));return Array.from(new Uint8Array(s),r=>r.toString(16).padStart(2,"0")).join("")}function v(i){try{let t=i.replace(/-/g,"+").replace(/_/g,"/");for(;t.length%4;)t+="=";let e=Uint8Array.from(atob(t),n=>n.charCodeAt(0));return e.length<9||e[0]!==33?null:Array.from(e.slice(1,9),n=>n.toString(16).padStart(2,"0")).join("")}catch{return null}}function T(i){let{creds:t,...e}=i,n=new p({...e,getClientIp:e.getClientIp??(s=>s.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||s.headers.get("x-real-ip")||"unknown")});return async s=>(await n.protect(s,t)).response}async function A(i,t){return i.scriptTag({nonce:t})}export{p as ObfiousV2,T as createObfiousMiddleware,A as obfiousScriptTag};
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@obfious/js",
3
+ "version": "0.1.1",
4
+ "description": "Obfious anti-bot protection for JavaScript — CF Workers, Next.js, Express, Fastify, Lambda",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./nextjs": {
12
+ "types": "./dist/nextjs.d.ts",
13
+ "import": "./dist/nextjs.js"
14
+ },
15
+ "./express": {
16
+ "types": "./dist/express.d.ts",
17
+ "import": "./dist/express.js"
18
+ },
19
+ "./fastify": {
20
+ "types": "./dist/fastify.d.ts",
21
+ "import": "./dist/fastify.js"
22
+ },
23
+ "./lambda": {
24
+ "types": "./dist/lambda.d.ts",
25
+ "import": "./dist/lambda.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "scripts": {
35
+ "build": "node build.mjs",
36
+ "prepublishOnly": "npm run build",
37
+ "ship": "npm version patch && npm publish --access public"
38
+ },
39
+ "devDependencies": {
40
+ "esbuild": "^0.27.4",
41
+ "typescript": "^5.7.0"
42
+ },
43
+ "license": "SEE LICENSE IN LICENSE"
44
+ }