bro-auth 0.1.0
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 +33 -0
- package/dist/index.cjs +76 -0
- package/dist/index.js +54 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
2
|
+
│ █▄▄ █▀█ █▀█ ▄▀█ █░█ ▀█▀ █░█ bro-auth │
|
|
3
|
+
│ █▄█ █▀▄ █▄█ █▀█ █▀█ ░█░ █▀█ │
|
|
4
|
+
├──────────────────────────────────────────────────────────────┤
|
|
5
|
+
│ Stateless JWT · Device Fingerprinting · Zero Replay │
|
|
6
|
+
└──────────────────────────────────────────────────────────────┘
|
|
7
|
+
|
|
8
|
+
# bro-auth
|
|
9
|
+
A lightweight, **stateless**, and **high-security** authentication layer using:
|
|
10
|
+
|
|
11
|
+
✅ JWT access tokens
|
|
12
|
+
✅ Refresh tokens
|
|
13
|
+
✅ Device fingerprint binding (prevents stolen-token replay)
|
|
14
|
+
✅ No database required
|
|
15
|
+
|
|
16
|
+
bro-auth aims to provide **DPoP-inspired protection** without the complexity.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 🚀 Features
|
|
21
|
+
|
|
22
|
+
- 🔐 **Stateless JWT authentication**
|
|
23
|
+
- 🆔 **Device fingerprint binding** (SHA-256 hashed)
|
|
24
|
+
- 🚫 **Replay attack protection** (tokens tied to a specific browser)
|
|
25
|
+
- ⚡ Lightweight, zero dependencies except `jsonwebtoken` + `crypto-es`
|
|
26
|
+
- 🧩 Works with ANY backend (Next.js, Express, Node HTTP)
|
|
27
|
+
- 🌐 Browser module provided for fingerprint extraction
|
|
28
|
+
- 📦 Ready for NPM consumption
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 Installation
|
|
33
|
+
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/browser/index.js
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
getFingerprint: () => getFingerprint
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(index_exports);
|
|
25
|
+
|
|
26
|
+
// src/browser/fingerprint.js
|
|
27
|
+
var import_crypto_es = require("crypto-es");
|
|
28
|
+
async function getCanvasFingerprint() {
|
|
29
|
+
try {
|
|
30
|
+
const canvas = document.createElement("canvas");
|
|
31
|
+
const ctx = canvas.getContext("2d");
|
|
32
|
+
ctx.textBaseline = "top";
|
|
33
|
+
ctx.font = "14px 'Arial'";
|
|
34
|
+
ctx.fillStyle = "#f60";
|
|
35
|
+
ctx.fillRect(0, 0, 100, 20);
|
|
36
|
+
ctx.fillStyle = "#000";
|
|
37
|
+
ctx.fillText("bro-auth-fingerprint", 2, 15);
|
|
38
|
+
return canvas.toDataURL();
|
|
39
|
+
} catch {
|
|
40
|
+
return "no-canvas";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function getGPUFingerprint() {
|
|
44
|
+
try {
|
|
45
|
+
const canvas = document.createElement("canvas");
|
|
46
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
47
|
+
if (!gl) return "no-webgl";
|
|
48
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
49
|
+
return debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : "no-renderer";
|
|
50
|
+
} catch {
|
|
51
|
+
return "no-webgl";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function getFingerprint() {
|
|
55
|
+
const components = {
|
|
56
|
+
userAgent: navigator.userAgent,
|
|
57
|
+
platform: navigator.platform,
|
|
58
|
+
language: navigator.language,
|
|
59
|
+
languages: navigator.languages.join(","),
|
|
60
|
+
screen: `${screen.width}x${screen.height}`,
|
|
61
|
+
colorDepth: screen.colorDepth,
|
|
62
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
63
|
+
timezoneOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
|
|
64
|
+
cpuCores: navigator.hardwareConcurrency || "unknown",
|
|
65
|
+
deviceMemory: navigator.deviceMemory || "unknown",
|
|
66
|
+
gpu: getGPUFingerprint(),
|
|
67
|
+
canvas: await getCanvasFingerprint()
|
|
68
|
+
};
|
|
69
|
+
const rawString = Object.values(components).join("|");
|
|
70
|
+
const fpHash = (0, import_crypto_es.SHA256)(rawString).toString();
|
|
71
|
+
return {
|
|
72
|
+
raw: rawString,
|
|
73
|
+
hash: fpHash,
|
|
74
|
+
components
|
|
75
|
+
};
|
|
76
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/browser/fingerprint.js
|
|
2
|
+
import { SHA256 } from "crypto-es";
|
|
3
|
+
async function getCanvasFingerprint() {
|
|
4
|
+
try {
|
|
5
|
+
const canvas = document.createElement("canvas");
|
|
6
|
+
const ctx = canvas.getContext("2d");
|
|
7
|
+
ctx.textBaseline = "top";
|
|
8
|
+
ctx.font = "14px 'Arial'";
|
|
9
|
+
ctx.fillStyle = "#f60";
|
|
10
|
+
ctx.fillRect(0, 0, 100, 20);
|
|
11
|
+
ctx.fillStyle = "#000";
|
|
12
|
+
ctx.fillText("bro-auth-fingerprint", 2, 15);
|
|
13
|
+
return canvas.toDataURL();
|
|
14
|
+
} catch {
|
|
15
|
+
return "no-canvas";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getGPUFingerprint() {
|
|
19
|
+
try {
|
|
20
|
+
const canvas = document.createElement("canvas");
|
|
21
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
22
|
+
if (!gl) return "no-webgl";
|
|
23
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
24
|
+
return debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : "no-renderer";
|
|
25
|
+
} catch {
|
|
26
|
+
return "no-webgl";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function getFingerprint() {
|
|
30
|
+
const components = {
|
|
31
|
+
userAgent: navigator.userAgent,
|
|
32
|
+
platform: navigator.platform,
|
|
33
|
+
language: navigator.language,
|
|
34
|
+
languages: navigator.languages.join(","),
|
|
35
|
+
screen: `${screen.width}x${screen.height}`,
|
|
36
|
+
colorDepth: screen.colorDepth,
|
|
37
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
38
|
+
timezoneOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
|
|
39
|
+
cpuCores: navigator.hardwareConcurrency || "unknown",
|
|
40
|
+
deviceMemory: navigator.deviceMemory || "unknown",
|
|
41
|
+
gpu: getGPUFingerprint(),
|
|
42
|
+
canvas: await getCanvasFingerprint()
|
|
43
|
+
};
|
|
44
|
+
const rawString = Object.values(components).join("|");
|
|
45
|
+
const fpHash = SHA256(rawString).toString();
|
|
46
|
+
return {
|
|
47
|
+
raw: rawString,
|
|
48
|
+
hash: fpHash,
|
|
49
|
+
components
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
getFingerprint
|
|
54
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bro-auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "bro-auth — Stateless, fingerprint-bound JWT authentication. Server utilities + browser fingerprinting module.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"browser": "dist/browser.mjs",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
},
|
|
14
|
+
"./browser": {
|
|
15
|
+
"import": "./dist/browser.js",
|
|
16
|
+
"require": "./dist/browser.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
,
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "rimraf dist",
|
|
27
|
+
"build:server": "tsup src/core/index.js --format cjs,esm --no-dts --out-dir dist",
|
|
28
|
+
"build:browser": "tsup src/browser/index.js --format cjs,esm --no-dts --out-dir dist --platform browser",
|
|
29
|
+
"build": "npm run clean && npm run build:server && npm run build:browser",
|
|
30
|
+
"prepack": "npm run build",
|
|
31
|
+
"test": "node --input-type=module -e \"import * as core from './dist/index.js'; console.log('core exports:', Object.keys(core));\""
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"jwt",
|
|
35
|
+
"authentication",
|
|
36
|
+
"browser fingerprint",
|
|
37
|
+
"token binding",
|
|
38
|
+
"security",
|
|
39
|
+
"bro-auth",
|
|
40
|
+
"auth"
|
|
41
|
+
],
|
|
42
|
+
"author": "Vaishnav",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"crypto-es": "^3.1.2",
|
|
46
|
+
"jsonwebtoken": "^9.0.2"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"rimraf": "^5.0.10",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|