py-auth-client 0.0.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/cache.d.ts +24 -0
- package/dist/cache.js +204 -0
- package/dist/client.d.ts +18 -0
- package/dist/client.js +202 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.js +95 -0
- package/dist/device.d.ts +5 -0
- package/dist/device.js +85 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +33 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.js +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.js +2 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# py-auth-client
|
|
2
|
+
|
|
3
|
+
TypeScript/Node.js client for **py-auth**. Compatible with the Python and Go clients in this repository.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i py-auth-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { AuthClient, AuthorizationError } from "py-auth-client";
|
|
15
|
+
|
|
16
|
+
const client = new AuthClient({
|
|
17
|
+
serverUrl: "http://localhost:8000",
|
|
18
|
+
softwareName: "我的软件",
|
|
19
|
+
clientSecret: process.env.CLIENT_SECRET!,
|
|
20
|
+
// debug: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await client.requireAuthorization();
|
|
25
|
+
console.log("authorized");
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (e instanceof AuthorizationError) {
|
|
28
|
+
console.error(e.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API
|
|
36
|
+
|
|
37
|
+
- `new AuthClient(config)`
|
|
38
|
+
- `client.checkAuthorization()`
|
|
39
|
+
- `client.requireAuthorization()`
|
|
40
|
+
- `client.getAuthorizationInfo()`
|
|
41
|
+
- `client.clearCache()`
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- Requires `clientSecret` or environment variable `CLIENT_SECRET`.
|
|
46
|
+
- Uses Fernet-compatible encryption derived from `CLIENT_SECRET`.
|
|
47
|
+
- Uses an obfuscated local cache file with a 7-day validity window.
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CacheRecord } from "./types";
|
|
2
|
+
export declare class AuthCache {
|
|
3
|
+
readonly cacheValiditySeconds: number;
|
|
4
|
+
readonly checkIntervalSeconds: number;
|
|
5
|
+
readonly cacheFile: string;
|
|
6
|
+
private readonly encryptKey;
|
|
7
|
+
private readonly deviceId;
|
|
8
|
+
private readonly softwareName;
|
|
9
|
+
private readonly cacheValidityDays;
|
|
10
|
+
constructor(args: {
|
|
11
|
+
cacheDir?: string;
|
|
12
|
+
deviceId: string;
|
|
13
|
+
serverUrl: string;
|
|
14
|
+
softwareName: string;
|
|
15
|
+
cacheValidityDays: number;
|
|
16
|
+
checkIntervalDays: number;
|
|
17
|
+
});
|
|
18
|
+
getCache(): CacheRecord | null;
|
|
19
|
+
saveCache(authorized: boolean, message: string): boolean;
|
|
20
|
+
isCacheValid(): boolean;
|
|
21
|
+
clearCache(): boolean;
|
|
22
|
+
private obfuscate;
|
|
23
|
+
private deobfuscate;
|
|
24
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AuthCache = void 0;
|
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
11
|
+
const node_zlib_1 = __importDefault(require("node:zlib"));
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
class AuthCache {
|
|
14
|
+
cacheValiditySeconds;
|
|
15
|
+
checkIntervalSeconds;
|
|
16
|
+
cacheFile;
|
|
17
|
+
encryptKey;
|
|
18
|
+
deviceId;
|
|
19
|
+
softwareName;
|
|
20
|
+
cacheValidityDays;
|
|
21
|
+
constructor(args) {
|
|
22
|
+
this.deviceId = args.deviceId;
|
|
23
|
+
this.softwareName = args.softwareName;
|
|
24
|
+
this.cacheValidityDays = args.cacheValidityDays;
|
|
25
|
+
this.cacheValiditySeconds = args.cacheValidityDays * 24 * 60 * 60;
|
|
26
|
+
this.checkIntervalSeconds = args.checkIntervalDays * 24 * 60 * 60;
|
|
27
|
+
const cacheDir = args.cacheDir ?? defaultCacheDir();
|
|
28
|
+
try {
|
|
29
|
+
node_fs_1.default.mkdirSync(cacheDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// ignore
|
|
33
|
+
}
|
|
34
|
+
const cacheKey = `${args.deviceId}:${args.softwareName}`;
|
|
35
|
+
const fileHash = node_crypto_1.default.createHash("md5").update(cacheKey, "utf8").digest("hex").slice(0, 12);
|
|
36
|
+
this.cacheFile = node_path_1.default.join(cacheDir, `runtime_${fileHash}.dat`);
|
|
37
|
+
const material = `${args.serverUrl}:${args.deviceId}:${args.softwareName}:obfuscate_v1`;
|
|
38
|
+
this.encryptKey = node_crypto_1.default.createHash("sha256").update(material, "utf8").digest();
|
|
39
|
+
}
|
|
40
|
+
getCache() {
|
|
41
|
+
try {
|
|
42
|
+
if (!node_fs_1.default.existsSync(this.cacheFile))
|
|
43
|
+
return null;
|
|
44
|
+
const encrypted = node_fs_1.default.readFileSync(this.cacheFile);
|
|
45
|
+
const decrypted = this.deobfuscate(encrypted);
|
|
46
|
+
if (!decrypted)
|
|
47
|
+
return null;
|
|
48
|
+
const raw = JSON.parse(decrypted.toString("utf8"));
|
|
49
|
+
return {
|
|
50
|
+
authorized: !!raw.a,
|
|
51
|
+
message: raw.m ?? "",
|
|
52
|
+
cachedAt: Number(raw.c ?? 0),
|
|
53
|
+
lastCheck: Number(raw.l ?? 0),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
saveCache(authorized, message) {
|
|
61
|
+
try {
|
|
62
|
+
const now = Date.now() / 1000;
|
|
63
|
+
const timeStr = formatPythonTimeString(now);
|
|
64
|
+
const fake = node_crypto_1.default.createHash("md5").update(timeStr, "utf8").digest("hex").slice(0, 8);
|
|
65
|
+
const payload = {
|
|
66
|
+
a: authorized,
|
|
67
|
+
m: message,
|
|
68
|
+
c: now,
|
|
69
|
+
l: now,
|
|
70
|
+
v: 2,
|
|
71
|
+
f: fake,
|
|
72
|
+
};
|
|
73
|
+
const json = JSON.stringify(payload);
|
|
74
|
+
const encrypted = this.obfuscate(Buffer.from(json, "utf8"));
|
|
75
|
+
if (!encrypted)
|
|
76
|
+
return false;
|
|
77
|
+
try {
|
|
78
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(this.cacheFile), { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// ignore
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
node_fs_1.default.writeFileSync(this.cacheFile, encrypted);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
try {
|
|
88
|
+
if (node_fs_1.default.existsSync(this.cacheFile))
|
|
89
|
+
node_fs_1.default.unlinkSync(this.cacheFile);
|
|
90
|
+
node_fs_1.default.writeFileSync(this.cacheFile, encrypted);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (process.platform === "win32") {
|
|
97
|
+
try {
|
|
98
|
+
(0, node_child_process_1.execFileSync)("attrib", ["+H", this.cacheFile], { stdio: "ignore" });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// ignore
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
isCacheValid() {
|
|
111
|
+
const cache = this.getCache();
|
|
112
|
+
if (!cache)
|
|
113
|
+
return false;
|
|
114
|
+
const elapsed = Date.now() / 1000 - cache.cachedAt;
|
|
115
|
+
return elapsed < this.cacheValiditySeconds;
|
|
116
|
+
}
|
|
117
|
+
clearCache() {
|
|
118
|
+
try {
|
|
119
|
+
if (node_fs_1.default.existsSync(this.cacheFile))
|
|
120
|
+
node_fs_1.default.unlinkSync(this.cacheFile);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
obfuscate(data) {
|
|
128
|
+
try {
|
|
129
|
+
const compressed = node_zlib_1.default.deflateSync(data, { level: 9 });
|
|
130
|
+
const xored = xorWithKey(compressed, this.encryptKey);
|
|
131
|
+
const timeSeed = Math.floor(Date.now() / 1000 / 3600);
|
|
132
|
+
const prefixSeed = node_crypto_1.default
|
|
133
|
+
.createHash("md5")
|
|
134
|
+
.update(`${this.deviceId}:${this.softwareName}:${timeSeed}`, "utf8")
|
|
135
|
+
.digest()
|
|
136
|
+
.subarray(0, 4);
|
|
137
|
+
const lenBuf = Buffer.alloc(4);
|
|
138
|
+
lenBuf.writeUInt32BE(xored.length, 0);
|
|
139
|
+
const packed = Buffer.concat([prefixSeed, lenBuf, xored]);
|
|
140
|
+
const finalKey = node_crypto_1.default.createHash("sha256").update(Buffer.concat([this.encryptKey, prefixSeed])).digest();
|
|
141
|
+
return xorWithKey(packed, finalKey);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
deobfuscate(data) {
|
|
148
|
+
if (data.length < 8)
|
|
149
|
+
return null;
|
|
150
|
+
const currentHour = Math.floor(Date.now() / 1000 / 3600);
|
|
151
|
+
const maxOffset = Math.max(2, this.cacheValidityDays * 24 + 12);
|
|
152
|
+
for (let hourOffset = -maxOffset; hourOffset <= maxOffset; hourOffset++) {
|
|
153
|
+
const timeSeed = currentHour + hourOffset;
|
|
154
|
+
const prefixSeed = node_crypto_1.default
|
|
155
|
+
.createHash("md5")
|
|
156
|
+
.update(`${this.deviceId}:${this.softwareName}:${timeSeed}`, "utf8")
|
|
157
|
+
.digest()
|
|
158
|
+
.subarray(0, 4);
|
|
159
|
+
const finalKey = node_crypto_1.default.createHash("sha256").update(Buffer.concat([this.encryptKey, prefixSeed])).digest();
|
|
160
|
+
const unpacked = xorWithKey(data, finalKey);
|
|
161
|
+
if (!unpacked.subarray(0, 4).equals(prefixSeed))
|
|
162
|
+
continue;
|
|
163
|
+
const length = unpacked.readUInt32BE(4);
|
|
164
|
+
if (length > unpacked.length - 8)
|
|
165
|
+
continue;
|
|
166
|
+
const xored = unpacked.subarray(8, 8 + length);
|
|
167
|
+
const compressed = xorWithKey(xored, this.encryptKey);
|
|
168
|
+
try {
|
|
169
|
+
return node_zlib_1.default.inflateSync(compressed);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
exports.AuthCache = AuthCache;
|
|
179
|
+
function xorWithKey(data, key) {
|
|
180
|
+
const out = Buffer.allocUnsafe(data.length);
|
|
181
|
+
for (let i = 0; i < data.length; i++) {
|
|
182
|
+
out[i] = data[i] ^ key[i % key.length];
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function defaultCacheDir() {
|
|
187
|
+
const home = node_os_1.default.homedir();
|
|
188
|
+
if (process.platform === "win32") {
|
|
189
|
+
const local = process.env.LOCALAPPDATA ?? node_path_1.default.join(home, "AppData", "Local");
|
|
190
|
+
return node_path_1.default.join(local, "Microsoft", "CLR_v4.0");
|
|
191
|
+
}
|
|
192
|
+
if (process.platform === "darwin") {
|
|
193
|
+
return node_path_1.default.join(home, "Library", "Caches", ".com.apple.metadata");
|
|
194
|
+
}
|
|
195
|
+
return node_path_1.default.join(home, ".cache", ".fontconfig");
|
|
196
|
+
}
|
|
197
|
+
function formatPythonTimeString(nowSeconds) {
|
|
198
|
+
// 目标:模拟 Python str(time.time()) 的大致风格(最短表示,必要时带 .0)
|
|
199
|
+
// 这里按 Go 实现保持一致:g/最短,但确保有小数点或 e
|
|
200
|
+
let s = String(nowSeconds);
|
|
201
|
+
if (!s.includes(".") && !s.includes("e"))
|
|
202
|
+
s += ".0";
|
|
203
|
+
return s;
|
|
204
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AuthClientConfig, AuthResult, AuthorizationInfo } from "./types";
|
|
2
|
+
export declare class AuthClient {
|
|
3
|
+
private readonly serverUrl;
|
|
4
|
+
private readonly softwareName;
|
|
5
|
+
private readonly deviceId;
|
|
6
|
+
private readonly deviceInfo;
|
|
7
|
+
private readonly clientSecret;
|
|
8
|
+
private readonly debug;
|
|
9
|
+
private readonly cache?;
|
|
10
|
+
constructor(config: AuthClientConfig);
|
|
11
|
+
private logDebug;
|
|
12
|
+
private formatRemainingTime;
|
|
13
|
+
private checkOnline;
|
|
14
|
+
checkAuthorization(): Promise<AuthResult>;
|
|
15
|
+
requireAuthorization(): Promise<boolean>;
|
|
16
|
+
clearCache(): boolean;
|
|
17
|
+
getAuthorizationInfo(): Promise<AuthorizationInfo>;
|
|
18
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthClient = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const crypto_1 = require("./crypto");
|
|
6
|
+
const cache_1 = require("./cache");
|
|
7
|
+
const device_1 = require("./device");
|
|
8
|
+
const http_1 = require("./http");
|
|
9
|
+
class AuthClient {
|
|
10
|
+
serverUrl;
|
|
11
|
+
softwareName;
|
|
12
|
+
deviceId;
|
|
13
|
+
deviceInfo;
|
|
14
|
+
clientSecret;
|
|
15
|
+
debug;
|
|
16
|
+
cache;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
if (!config.serverUrl)
|
|
19
|
+
throw new Error("serverUrl不能为空");
|
|
20
|
+
if (!config.softwareName)
|
|
21
|
+
throw new Error("softwareName不能为空");
|
|
22
|
+
const secret = config.clientSecret ?? process.env.CLIENT_SECRET ?? "";
|
|
23
|
+
if (!secret) {
|
|
24
|
+
throw new Error("CLIENT_SECRET未配置!请在初始化时传入clientSecret参数,或设置环境变量CLIENT_SECRET。这是安全要求,必须配置。");
|
|
25
|
+
}
|
|
26
|
+
this.serverUrl = config.serverUrl.replace(/\/+$/, "");
|
|
27
|
+
this.softwareName = config.softwareName;
|
|
28
|
+
this.clientSecret = secret;
|
|
29
|
+
this.debug = !!config.debug;
|
|
30
|
+
this.deviceId = (0, device_1.buildDeviceId)(this.serverUrl, config.deviceId, this.softwareName);
|
|
31
|
+
this.deviceInfo = config.deviceInfo ?? (0, device_1.collectDeviceInfo)();
|
|
32
|
+
const enableCache = config.enableCache ?? true;
|
|
33
|
+
const cacheValidityDays = config.cacheValidityDays ?? 7;
|
|
34
|
+
const checkIntervalDays = config.checkIntervalDays ?? 2;
|
|
35
|
+
if (enableCache) {
|
|
36
|
+
this.cache = new cache_1.AuthCache({
|
|
37
|
+
cacheDir: config.cacheDir,
|
|
38
|
+
deviceId: this.deviceId,
|
|
39
|
+
serverUrl: this.serverUrl,
|
|
40
|
+
softwareName: this.softwareName,
|
|
41
|
+
cacheValidityDays,
|
|
42
|
+
checkIntervalDays,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
logDebug(msg) {
|
|
47
|
+
if (this.debug) {
|
|
48
|
+
// 不加额外依赖,直接输出
|
|
49
|
+
// eslint-disable-next-line no-console
|
|
50
|
+
console.debug(`[ts-auth-client][DEBUG] ${msg}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
formatRemainingTime(cachedAtSeconds) {
|
|
54
|
+
if (!cachedAtSeconds || cachedAtSeconds <= 0 || !this.cache)
|
|
55
|
+
return "未知";
|
|
56
|
+
const now = Date.now() / 1000;
|
|
57
|
+
const elapsed = now - cachedAtSeconds;
|
|
58
|
+
const remaining = this.cache.cacheValiditySeconds - elapsed;
|
|
59
|
+
if (remaining <= 0)
|
|
60
|
+
return "已过期";
|
|
61
|
+
const days = Math.floor(remaining / 86400);
|
|
62
|
+
const hours = Math.floor((remaining % 86400) / 3600);
|
|
63
|
+
const minutes = Math.floor((remaining % 3600) / 60);
|
|
64
|
+
const parts = [];
|
|
65
|
+
if (days > 0)
|
|
66
|
+
parts.push(`${days}天`);
|
|
67
|
+
if (hours > 0)
|
|
68
|
+
parts.push(`${hours}小时`);
|
|
69
|
+
if (minutes > 0 || parts.length === 0)
|
|
70
|
+
parts.push(`${minutes}分钟`);
|
|
71
|
+
return parts.join("");
|
|
72
|
+
}
|
|
73
|
+
async checkOnline() {
|
|
74
|
+
try {
|
|
75
|
+
this.logDebug("开始在线订阅请求...");
|
|
76
|
+
const requestData = {
|
|
77
|
+
device_id: this.deviceId,
|
|
78
|
+
software_name: this.softwareName,
|
|
79
|
+
device_info: this.deviceInfo,
|
|
80
|
+
};
|
|
81
|
+
const encrypted = (0, crypto_1.encryptData)(JSON.stringify(requestData), this.clientSecret);
|
|
82
|
+
const { status, json, text } = await (0, http_1.postJson)(`${this.serverUrl}/api/auth/heartbeat`, { encrypted_data: encrypted }, 10_000);
|
|
83
|
+
if (status === 200) {
|
|
84
|
+
const token = json?.encrypted_data ?? "";
|
|
85
|
+
const decryptedText = token ? (0, crypto_1.decryptData)(token, this.clientSecret) : null;
|
|
86
|
+
if (!decryptedText) {
|
|
87
|
+
this.logDebug("在线订阅响应解密失败");
|
|
88
|
+
return { authorized: false, message: "解密响应失败", success: false, from_cache: false };
|
|
89
|
+
}
|
|
90
|
+
const decrypted = JSON.parse(decryptedText);
|
|
91
|
+
this.logDebug(`在线订阅成功,authorized=${!!decrypted.authorized}`);
|
|
92
|
+
return {
|
|
93
|
+
authorized: !!decrypted.authorized,
|
|
94
|
+
message: decrypted.message ?? "",
|
|
95
|
+
success: true,
|
|
96
|
+
from_cache: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Python 逻辑:403 取 detail,其它返回 “服务器错误: status”
|
|
100
|
+
const detail = json?.detail;
|
|
101
|
+
const msg = status === 403 && typeof detail === "string" ? detail : `服务器错误: ${status}`;
|
|
102
|
+
this.logDebug(`在线订阅失败,status=${status}, message=${msg}; raw=${text}`);
|
|
103
|
+
return { authorized: false, message: msg, success: false, from_cache: false, is_auth_error: status === 403 };
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
const msg = e?.name === "AbortError" ? "连接失败: timeout" : `连接失败: ${String(e?.message ?? e)}`;
|
|
107
|
+
this.logDebug(`在线订阅请求异常: ${msg}`);
|
|
108
|
+
return { authorized: false, message: msg, success: false, from_cache: false };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async checkAuthorization() {
|
|
112
|
+
if (!this.cache) {
|
|
113
|
+
return this.checkOnline();
|
|
114
|
+
}
|
|
115
|
+
let cacheData = this.cache.getCache();
|
|
116
|
+
let cacheValid = false;
|
|
117
|
+
if (cacheData?.cachedAt) {
|
|
118
|
+
const elapsed = Date.now() / 1000 - cacheData.cachedAt;
|
|
119
|
+
if (elapsed < this.cache.cacheValiditySeconds) {
|
|
120
|
+
cacheValid = true;
|
|
121
|
+
this.logDebug("命中有效缓存,直接授权通过");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (cacheValid) {
|
|
125
|
+
this.logDebug("缓存有效,继续尝试在线订阅来更新订阅");
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.logDebug(cacheData ? "缓存存在但已过期,准备发起在线订阅请求" : "未找到缓存,准备发起在线订阅请求");
|
|
129
|
+
}
|
|
130
|
+
const onlineResult = await this.checkOnline();
|
|
131
|
+
if (onlineResult.success) {
|
|
132
|
+
this.logDebug("在线订阅成功,更新缓存");
|
|
133
|
+
this.cache.saveCache(onlineResult.authorized, onlineResult.message);
|
|
134
|
+
return onlineResult;
|
|
135
|
+
}
|
|
136
|
+
if (cacheValid && cacheData) {
|
|
137
|
+
const remaining = this.formatRemainingTime(cacheData.cachedAt);
|
|
138
|
+
this.logDebug(`在线订阅失败,但缓存有效,使用缓存结果,订阅剩余时间: ${remaining}`);
|
|
139
|
+
return {
|
|
140
|
+
authorized: cacheData.authorized,
|
|
141
|
+
message: cacheData.message,
|
|
142
|
+
success: true,
|
|
143
|
+
from_cache: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return onlineResult;
|
|
147
|
+
}
|
|
148
|
+
async requireAuthorization() {
|
|
149
|
+
const result = await this.checkAuthorization();
|
|
150
|
+
if (!result.success) {
|
|
151
|
+
throw new errors_1.AuthorizationError({
|
|
152
|
+
message: result.message,
|
|
153
|
+
result,
|
|
154
|
+
deviceId: this.deviceId,
|
|
155
|
+
serverUrl: this.serverUrl,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!result.authorized) {
|
|
159
|
+
throw new errors_1.AuthorizationError({
|
|
160
|
+
message: result.message,
|
|
161
|
+
result,
|
|
162
|
+
deviceId: this.deviceId,
|
|
163
|
+
serverUrl: this.serverUrl,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
clearCache() {
|
|
169
|
+
if (!this.cache)
|
|
170
|
+
return true;
|
|
171
|
+
return this.cache.clearCache();
|
|
172
|
+
}
|
|
173
|
+
async getAuthorizationInfo() {
|
|
174
|
+
const result = await this.checkAuthorization();
|
|
175
|
+
const info = {
|
|
176
|
+
authorized: result.authorized,
|
|
177
|
+
success: result.success,
|
|
178
|
+
from_cache: result.from_cache,
|
|
179
|
+
message: result.message,
|
|
180
|
+
device_id: this.deviceId,
|
|
181
|
+
server_url: this.serverUrl,
|
|
182
|
+
};
|
|
183
|
+
if (this.cache) {
|
|
184
|
+
const cache = this.cache.getCache();
|
|
185
|
+
if (cache) {
|
|
186
|
+
const remaining = this.formatRemainingTime(cache.cachedAt);
|
|
187
|
+
info.remaining_time = remaining;
|
|
188
|
+
info.cache_valid = this.cache.isCacheValid();
|
|
189
|
+
info.cached_at = cache.cachedAt;
|
|
190
|
+
if (cache.cachedAt > 0) {
|
|
191
|
+
info.cached_at_readable = new Date(cache.cachedAt * 1000).toISOString().replace("T", " ").slice(0, 19);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
info.remaining_time = "无缓存";
|
|
196
|
+
info.cache_valid = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return info;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.AuthClient = AuthClient;
|
package/dist/crypto.d.ts
ADDED
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encryptData = encryptData;
|
|
4
|
+
exports.decryptData = decryptData;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
function b64UrlEncode(buf) {
|
|
7
|
+
return buf
|
|
8
|
+
.toString("base64")
|
|
9
|
+
.replace(/\+/g, "-")
|
|
10
|
+
.replace(/\//g, "_")
|
|
11
|
+
.replace(/=+$/g, "");
|
|
12
|
+
}
|
|
13
|
+
function b64UrlDecode(s) {
|
|
14
|
+
const padded = s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4);
|
|
15
|
+
return Buffer.from(padded, "base64");
|
|
16
|
+
}
|
|
17
|
+
function timingSafeEqual(a, b) {
|
|
18
|
+
if (a.length !== b.length)
|
|
19
|
+
return false;
|
|
20
|
+
let diff = 0;
|
|
21
|
+
for (let i = 0; i < a.length; i++)
|
|
22
|
+
diff |= a[i] ^ b[i];
|
|
23
|
+
return diff === 0;
|
|
24
|
+
}
|
|
25
|
+
function deriveFernetKey(clientSecret) {
|
|
26
|
+
const digest = (0, node_crypto_1.createHash)("sha256").update(Buffer.from(clientSecret, "utf8")).digest();
|
|
27
|
+
return digest.subarray(0, 32);
|
|
28
|
+
}
|
|
29
|
+
function pkcs7Pad(data, blockSize = 16) {
|
|
30
|
+
const padLen = blockSize - (data.length % blockSize || blockSize);
|
|
31
|
+
return Buffer.concat([data, Buffer.alloc(padLen, padLen)]);
|
|
32
|
+
}
|
|
33
|
+
function pkcs7Unpad(data, blockSize = 16) {
|
|
34
|
+
if (data.length === 0 || data.length % blockSize !== 0)
|
|
35
|
+
return null;
|
|
36
|
+
const padLen = data[data.length - 1];
|
|
37
|
+
if (padLen <= 0 || padLen > blockSize)
|
|
38
|
+
return null;
|
|
39
|
+
for (let i = 0; i < padLen; i++) {
|
|
40
|
+
if (data[data.length - 1 - i] !== padLen)
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return data.subarray(0, data.length - padLen);
|
|
44
|
+
}
|
|
45
|
+
function aesCbcEncrypt(key, iv, plaintext) {
|
|
46
|
+
const cipher = require("node:crypto").createCipheriv("aes-128-cbc", key, iv);
|
|
47
|
+
cipher.setAutoPadding(false);
|
|
48
|
+
const padded = pkcs7Pad(plaintext, 16);
|
|
49
|
+
return Buffer.concat([cipher.update(padded), cipher.final()]);
|
|
50
|
+
}
|
|
51
|
+
function aesCbcDecrypt(key, iv, ciphertext) {
|
|
52
|
+
const decipher = require("node:crypto").createDecipheriv("aes-128-cbc", key, iv);
|
|
53
|
+
decipher.setAutoPadding(false);
|
|
54
|
+
const padded = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
55
|
+
return pkcs7Unpad(padded, 16);
|
|
56
|
+
}
|
|
57
|
+
function encryptData(plaintextUtf8Json, clientSecret) {
|
|
58
|
+
if (!clientSecret)
|
|
59
|
+
throw new Error("client_secret不能为空");
|
|
60
|
+
const key = deriveFernetKey(clientSecret);
|
|
61
|
+
const signingKey = key.subarray(0, 16);
|
|
62
|
+
const encryptionKey = key.subarray(16, 32);
|
|
63
|
+
const version = Buffer.from([0x80]);
|
|
64
|
+
const timestamp = Buffer.alloc(8);
|
|
65
|
+
const ts = BigInt(Math.floor(Date.now() / 1000));
|
|
66
|
+
timestamp.writeBigUInt64BE(ts, 0);
|
|
67
|
+
const iv = (0, node_crypto_1.randomBytes)(16);
|
|
68
|
+
const ciphertext = aesCbcEncrypt(encryptionKey, iv, Buffer.from(plaintextUtf8Json, "utf8"));
|
|
69
|
+
const payload = Buffer.concat([version, timestamp, iv, ciphertext]);
|
|
70
|
+
const hmac = (0, node_crypto_1.createHmac)("sha256", signingKey).update(payload).digest();
|
|
71
|
+
return b64UrlEncode(Buffer.concat([payload, hmac]));
|
|
72
|
+
}
|
|
73
|
+
function decryptData(token, clientSecret) {
|
|
74
|
+
if (!clientSecret)
|
|
75
|
+
throw new Error("client_secret不能为空");
|
|
76
|
+
const raw = b64UrlDecode(token);
|
|
77
|
+
if (raw.length < 1 + 8 + 16 + 32)
|
|
78
|
+
return null;
|
|
79
|
+
const key = deriveFernetKey(clientSecret);
|
|
80
|
+
const signingKey = key.subarray(0, 16);
|
|
81
|
+
const encryptionKey = key.subarray(16, 32);
|
|
82
|
+
const payload = raw.subarray(0, raw.length - 32);
|
|
83
|
+
const mac = raw.subarray(raw.length - 32);
|
|
84
|
+
const expected = (0, node_crypto_1.createHmac)("sha256", signingKey).update(payload).digest();
|
|
85
|
+
if (!timingSafeEqual(mac, expected))
|
|
86
|
+
return null;
|
|
87
|
+
if (payload[0] !== 0x80)
|
|
88
|
+
return null;
|
|
89
|
+
const iv = payload.subarray(1 + 8, 1 + 8 + 16);
|
|
90
|
+
const ciphertext = payload.subarray(1 + 8 + 16);
|
|
91
|
+
const plaintext = aesCbcDecrypt(encryptionKey, iv, ciphertext);
|
|
92
|
+
if (!plaintext)
|
|
93
|
+
return null;
|
|
94
|
+
return plaintext.toString("utf8");
|
|
95
|
+
}
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DeviceInfo } from "./types";
|
|
2
|
+
export declare function loadPersistedDeviceId(serverUrl: string, softwareName: string): string | null;
|
|
3
|
+
export declare function persistDeviceId(serverUrl: string, softwareName: string, deviceId: string): void;
|
|
4
|
+
export declare function buildDeviceId(serverUrl: string, providedDeviceId: string | undefined, softwareName: string): string;
|
|
5
|
+
export declare function collectDeviceInfo(): DeviceInfo;
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadPersistedDeviceId = loadPersistedDeviceId;
|
|
7
|
+
exports.persistDeviceId = persistDeviceId;
|
|
8
|
+
exports.buildDeviceId = buildDeviceId;
|
|
9
|
+
exports.collectDeviceInfo = collectDeviceInfo;
|
|
10
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
13
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
+
const node_machine_id_1 = require("node-machine-id");
|
|
15
|
+
function deviceIdStorePath(serverUrl, softwareName) {
|
|
16
|
+
const home = node_os_1.default.homedir();
|
|
17
|
+
const base = node_path_1.default.join(home, ".py_auth_device");
|
|
18
|
+
try {
|
|
19
|
+
node_fs_1.default.mkdirSync(base, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// ignore
|
|
23
|
+
}
|
|
24
|
+
const serverHash = node_crypto_1.default.createHash("sha256").update(serverUrl, "utf8").digest("hex").slice(0, 12);
|
|
25
|
+
const softwareHash = softwareName
|
|
26
|
+
? node_crypto_1.default.createHash("sha256").update(softwareName, "utf8").digest("hex").slice(0, 8)
|
|
27
|
+
: "default";
|
|
28
|
+
return node_path_1.default.join(base, `device_${serverHash}_${softwareHash}.txt`);
|
|
29
|
+
}
|
|
30
|
+
function loadPersistedDeviceId(serverUrl, softwareName) {
|
|
31
|
+
try {
|
|
32
|
+
const p = deviceIdStorePath(serverUrl, softwareName);
|
|
33
|
+
if (!node_fs_1.default.existsSync(p))
|
|
34
|
+
return null;
|
|
35
|
+
const content = node_fs_1.default.readFileSync(p, "utf8").trim();
|
|
36
|
+
return content || null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function persistDeviceId(serverUrl, softwareName, deviceId) {
|
|
43
|
+
try {
|
|
44
|
+
const p = deviceIdStorePath(serverUrl, softwareName);
|
|
45
|
+
node_fs_1.default.writeFileSync(p, deviceId, "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function buildDeviceId(serverUrl, providedDeviceId, softwareName) {
|
|
52
|
+
if (providedDeviceId) {
|
|
53
|
+
persistDeviceId(serverUrl, softwareName, providedDeviceId);
|
|
54
|
+
return providedDeviceId;
|
|
55
|
+
}
|
|
56
|
+
const persisted = loadPersistedDeviceId(serverUrl, softwareName);
|
|
57
|
+
if (persisted)
|
|
58
|
+
return persisted;
|
|
59
|
+
// Node 环境下用机器 ID 做稳定输入;再混入 softwareName 保证同机不同软件不同 device_id
|
|
60
|
+
let base = "";
|
|
61
|
+
try {
|
|
62
|
+
base = (0, node_machine_id_1.machineIdSync)();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
base = node_crypto_1.default.randomUUID();
|
|
66
|
+
}
|
|
67
|
+
const deviceId = node_crypto_1.default.createHash("sha256").update(`${base}-${softwareName}`, "utf8").digest("hex").slice(0, 32);
|
|
68
|
+
persistDeviceId(serverUrl, softwareName, deviceId);
|
|
69
|
+
return deviceId;
|
|
70
|
+
}
|
|
71
|
+
function collectDeviceInfo() {
|
|
72
|
+
const info = {
|
|
73
|
+
hostname: node_os_1.default.hostname(),
|
|
74
|
+
system: node_os_1.default.platform(),
|
|
75
|
+
release: node_os_1.default.release(),
|
|
76
|
+
machine: node_os_1.default.arch(),
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
info.username = node_os_1.default.userInfo().username;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// ignore
|
|
83
|
+
}
|
|
84
|
+
return info;
|
|
85
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuthResult } from "./types";
|
|
2
|
+
export declare class AuthorizationError extends Error {
|
|
3
|
+
readonly result?: AuthResult;
|
|
4
|
+
readonly deviceId?: string;
|
|
5
|
+
readonly serverUrl?: string;
|
|
6
|
+
constructor(args: {
|
|
7
|
+
message: string;
|
|
8
|
+
result?: AuthResult;
|
|
9
|
+
deviceId?: string;
|
|
10
|
+
serverUrl?: string;
|
|
11
|
+
});
|
|
12
|
+
get isNetworkError(): boolean;
|
|
13
|
+
get isUnauthorized(): boolean;
|
|
14
|
+
get isValidationError(): boolean;
|
|
15
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthorizationError = void 0;
|
|
4
|
+
class AuthorizationError extends Error {
|
|
5
|
+
result;
|
|
6
|
+
deviceId;
|
|
7
|
+
serverUrl;
|
|
8
|
+
constructor(args) {
|
|
9
|
+
super(args.message);
|
|
10
|
+
this.name = "AuthorizationError";
|
|
11
|
+
this.result = args.result;
|
|
12
|
+
this.deviceId = args.deviceId;
|
|
13
|
+
this.serverUrl = args.serverUrl;
|
|
14
|
+
}
|
|
15
|
+
get isNetworkError() {
|
|
16
|
+
const message = (this.result?.message ?? this.message).toLowerCase();
|
|
17
|
+
const keywords = ["连接失败", "连接", "network", "timeout", "connection"];
|
|
18
|
+
return keywords.some((k) => message.includes(k.toLowerCase()));
|
|
19
|
+
}
|
|
20
|
+
get isUnauthorized() {
|
|
21
|
+
if (this.result) {
|
|
22
|
+
return !this.result.authorized && this.result.success;
|
|
23
|
+
}
|
|
24
|
+
return this.message.includes("未授权") || this.message.includes("禁用");
|
|
25
|
+
}
|
|
26
|
+
get isValidationError() {
|
|
27
|
+
if (this.result) {
|
|
28
|
+
return !this.result.success;
|
|
29
|
+
}
|
|
30
|
+
return this.message.includes("无法验证授权") || this.message.includes("验证失败");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.AuthorizationError = AuthorizationError;
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.postJson = postJson;
|
|
4
|
+
async function postJson(url, body, timeoutMs) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(url, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: { "Content-Type": "application/json" },
|
|
11
|
+
body: JSON.stringify(body),
|
|
12
|
+
signal: controller.signal,
|
|
13
|
+
});
|
|
14
|
+
const text = await res.text();
|
|
15
|
+
let json = null;
|
|
16
|
+
try {
|
|
17
|
+
json = text ? JSON.parse(text) : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
json = null;
|
|
21
|
+
}
|
|
22
|
+
return { status: res.status, json, text };
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthorizationError = exports.AuthClient = void 0;
|
|
4
|
+
var client_1 = require("./client");
|
|
5
|
+
Object.defineProperty(exports, "AuthClient", { enumerable: true, get: function () { return client_1.AuthClient; } });
|
|
6
|
+
var errors_1 = require("./errors");
|
|
7
|
+
Object.defineProperty(exports, "AuthorizationError", { enumerable: true, get: function () { return errors_1.AuthorizationError; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface DeviceInfo {
|
|
2
|
+
hostname?: string;
|
|
3
|
+
system?: string;
|
|
4
|
+
release?: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
machine?: string;
|
|
7
|
+
processor?: string;
|
|
8
|
+
mac_address?: string;
|
|
9
|
+
ip_address?: string;
|
|
10
|
+
cpu_count?: number;
|
|
11
|
+
cpu_freq_mhz?: number;
|
|
12
|
+
memory_total_gb?: number;
|
|
13
|
+
disk_total_gb?: number;
|
|
14
|
+
username?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AuthResult {
|
|
17
|
+
authorized: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
success: boolean;
|
|
20
|
+
from_cache: boolean;
|
|
21
|
+
is_auth_error?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface AuthorizationInfo {
|
|
24
|
+
authorized: boolean;
|
|
25
|
+
success: boolean;
|
|
26
|
+
from_cache: boolean;
|
|
27
|
+
message: string;
|
|
28
|
+
device_id: string;
|
|
29
|
+
server_url: string;
|
|
30
|
+
remaining_time?: string;
|
|
31
|
+
cache_valid?: boolean;
|
|
32
|
+
cached_at?: number;
|
|
33
|
+
cached_at_readable?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface CacheRecordWire {
|
|
36
|
+
a: boolean;
|
|
37
|
+
m: string;
|
|
38
|
+
c: number;
|
|
39
|
+
l: number;
|
|
40
|
+
v: number;
|
|
41
|
+
f: string;
|
|
42
|
+
}
|
|
43
|
+
export interface CacheRecord {
|
|
44
|
+
authorized: boolean;
|
|
45
|
+
message: string;
|
|
46
|
+
cachedAt: number;
|
|
47
|
+
lastCheck: number;
|
|
48
|
+
}
|
|
49
|
+
export interface AuthClientConfig {
|
|
50
|
+
serverUrl: string;
|
|
51
|
+
softwareName: string;
|
|
52
|
+
deviceId?: string;
|
|
53
|
+
deviceInfo?: DeviceInfo;
|
|
54
|
+
clientSecret?: string;
|
|
55
|
+
cacheDir?: string;
|
|
56
|
+
enableCache?: boolean;
|
|
57
|
+
cacheValidityDays?: number;
|
|
58
|
+
checkIntervalDays?: number;
|
|
59
|
+
debug?: boolean;
|
|
60
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "py-auth-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "TypeScript/Node.js client for py-auth (compatible with Python/Go clients)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"auth",
|
|
7
|
+
"licensing",
|
|
8
|
+
"client",
|
|
9
|
+
"fernet"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/Paper-Dragon/py-auth.git",
|
|
14
|
+
"directory": "client/ts"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/Paper-Dragon/py-auth/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/Paper-Dragon/py-auth#readme",
|
|
20
|
+
"main": "dist/index.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"node-machine-id": "^1.1.12"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"tsx": "^4.21.0",
|
|
45
|
+
"typescript": "^5.5.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
51
|
+
"dev": "tsx example.ts"
|
|
52
|
+
}
|
|
53
|
+
}
|