blok0 0.1.0 → 0.1.2
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/dist/api/index.d.ts +46 -0
- package/dist/api/index.js +147 -0
- package/dist/ast/index.d.ts +31 -0
- package/dist/ast/index.js +324 -0
- package/dist/auth/constants.d.ts +11 -0
- package/dist/auth/constants.js +155 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.js +168 -0
- package/dist/auth/server.d.ts +55 -0
- package/dist/auth/server.js +236 -0
- package/dist/blocks/index.d.ts +56 -0
- package/dist/blocks/index.js +189 -0
- package/dist/handlers/add-block.d.ts +7 -0
- package/dist/handlers/add-block.js +142 -0
- package/dist/handlers/login.d.ts +8 -0
- package/dist/handlers/login.js +124 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +187 -7
- package/dist/registry/index.d.ts +75 -0
- package/dist/registry/index.js +231 -0
- package/package.json +33 -25
- package/src/api/index.ts +177 -0
- package/src/ast/index.ts +368 -0
- package/src/auth/constants.ts +155 -0
- package/src/auth/index.ts +154 -0
- package/src/auth/server.ts +240 -0
- package/src/blocks/index.ts +186 -0
- package/src/handlers/add-block.ts +132 -0
- package/src/handlers/login.ts +130 -0
- package/src/index.ts +212 -51
- package/src/registry/index.ts +244 -0
- package/test-ast.js +150 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TIMEOUT_HTML = exports.ERROR_HTML = exports.SUCCESS_HTML = exports.CALLBACK_PATH = exports.PORT_RANGE = exports.DEFAULT_TIMEOUT = exports.AUTHORIZE_ENDPOINT = exports.AUTH_BASE_URL = void 0;
|
|
4
|
+
exports.AUTH_BASE_URL = 'https://www.blok0.xyz';
|
|
5
|
+
exports.AUTHORIZE_ENDPOINT = '/api/authorize/cli';
|
|
6
|
+
exports.DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
7
|
+
exports.PORT_RANGE = { min: 3000, max: 4000 };
|
|
8
|
+
exports.CALLBACK_PATH = '/callback';
|
|
9
|
+
exports.SUCCESS_HTML = `
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html>
|
|
12
|
+
<head>
|
|
13
|
+
<title>Authentication Successful</title>
|
|
14
|
+
<style>
|
|
15
|
+
body {
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
17
|
+
background: #f5f5f5;
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
height: 100vh;
|
|
24
|
+
}
|
|
25
|
+
.container {
|
|
26
|
+
background: white;
|
|
27
|
+
padding: 2rem;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
30
|
+
text-align: center;
|
|
31
|
+
max-width: 400px;
|
|
32
|
+
}
|
|
33
|
+
.success-icon {
|
|
34
|
+
color: #10b981;
|
|
35
|
+
font-size: 3rem;
|
|
36
|
+
margin-bottom: 1rem;
|
|
37
|
+
}
|
|
38
|
+
h1 {
|
|
39
|
+
color: #1f2937;
|
|
40
|
+
margin: 0 0 0.5rem 0;
|
|
41
|
+
font-size: 1.5rem;
|
|
42
|
+
}
|
|
43
|
+
p {
|
|
44
|
+
color: #6b7280;
|
|
45
|
+
margin: 0;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
48
|
+
</head>
|
|
49
|
+
<body>
|
|
50
|
+
<div class="container">
|
|
51
|
+
<div class="success-icon">✓</div>
|
|
52
|
+
<h1>Authentication Successful!</h1>
|
|
53
|
+
<p>You can now close this window and return to your terminal.</p>
|
|
54
|
+
</div>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
`;
|
|
58
|
+
exports.ERROR_HTML = `
|
|
59
|
+
<!DOCTYPE html>
|
|
60
|
+
<html>
|
|
61
|
+
<head>
|
|
62
|
+
<title>Authentication Failed</title>
|
|
63
|
+
<style>
|
|
64
|
+
body {
|
|
65
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
66
|
+
background: #f5f5f5;
|
|
67
|
+
margin: 0;
|
|
68
|
+
padding: 0;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
height: 100vh;
|
|
73
|
+
}
|
|
74
|
+
.container {
|
|
75
|
+
background: white;
|
|
76
|
+
padding: 2rem;
|
|
77
|
+
border-radius: 8px;
|
|
78
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
79
|
+
text-align: center;
|
|
80
|
+
max-width: 400px;
|
|
81
|
+
}
|
|
82
|
+
.error-icon {
|
|
83
|
+
color: #ef4444;
|
|
84
|
+
font-size: 3rem;
|
|
85
|
+
margin-bottom: 1rem;
|
|
86
|
+
}
|
|
87
|
+
h1 {
|
|
88
|
+
color: #1f2937;
|
|
89
|
+
margin: 0 0 0.5rem 0;
|
|
90
|
+
font-size: 1.5rem;
|
|
91
|
+
}
|
|
92
|
+
p {
|
|
93
|
+
color: #6b7280;
|
|
94
|
+
margin: 0;
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<div class="container">
|
|
100
|
+
<div class="error-icon">✗</div>
|
|
101
|
+
<h1>Authentication Failed</h1>
|
|
102
|
+
<p>Please try again or contact support if the problem persists.</p>
|
|
103
|
+
</div>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
|
106
|
+
`;
|
|
107
|
+
exports.TIMEOUT_HTML = `
|
|
108
|
+
<!DOCTYPE html>
|
|
109
|
+
<html>
|
|
110
|
+
<head>
|
|
111
|
+
<title>Authentication Timeout</title>
|
|
112
|
+
<style>
|
|
113
|
+
body {
|
|
114
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
115
|
+
background: #f5f5f5;
|
|
116
|
+
margin: 0;
|
|
117
|
+
padding: 0;
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
height: 100vh;
|
|
122
|
+
}
|
|
123
|
+
.container {
|
|
124
|
+
background: white;
|
|
125
|
+
padding: 2rem;
|
|
126
|
+
border-radius: 8px;
|
|
127
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
128
|
+
text-align: center;
|
|
129
|
+
max-width: 400px;
|
|
130
|
+
}
|
|
131
|
+
.timeout-icon {
|
|
132
|
+
color: #f59e0b;
|
|
133
|
+
font-size: 3rem;
|
|
134
|
+
margin-bottom: 1rem;
|
|
135
|
+
}
|
|
136
|
+
h1 {
|
|
137
|
+
color: #1f2937;
|
|
138
|
+
margin: 0 0 0.5rem 0;
|
|
139
|
+
font-size: 1.5rem;
|
|
140
|
+
}
|
|
141
|
+
p {
|
|
142
|
+
color: #6b7280;
|
|
143
|
+
margin: 0;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
146
|
+
</head>
|
|
147
|
+
<body>
|
|
148
|
+
<div class="container">
|
|
149
|
+
<div class="timeout-icon">⏱</div>
|
|
150
|
+
<h1>Authentication Timeout</h1>
|
|
151
|
+
<p>The authentication request has timed out. Please try again.</p>
|
|
152
|
+
</div>
|
|
153
|
+
</body>
|
|
154
|
+
</html>
|
|
155
|
+
`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface AuthConfig {
|
|
2
|
+
apiEndpoint?: string;
|
|
3
|
+
refreshToken?: string;
|
|
4
|
+
tokenExpiry?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface AuthCallback {
|
|
7
|
+
token: string;
|
|
8
|
+
expires_in?: number;
|
|
9
|
+
refresh_token?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AuthServerOptions {
|
|
12
|
+
port?: number;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
state?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TokenResponse {
|
|
17
|
+
access_token: string;
|
|
18
|
+
refresh_token?: string;
|
|
19
|
+
expires_in?: number;
|
|
20
|
+
token_type?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load auth configuration from file
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadAuthConfig(): AuthConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Save auth configuration to file
|
|
28
|
+
*/
|
|
29
|
+
export declare function saveAuthConfig(config: AuthConfig): void;
|
|
30
|
+
/**
|
|
31
|
+
* Store access token securely
|
|
32
|
+
*/
|
|
33
|
+
export declare function storeAccessToken(token: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Get stored access token
|
|
36
|
+
*/
|
|
37
|
+
export declare function getAccessToken(): Promise<string | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Store refresh token securely
|
|
40
|
+
*/
|
|
41
|
+
export declare function storeRefreshToken(token: string): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Get stored refresh token
|
|
44
|
+
*/
|
|
45
|
+
export declare function getRefreshToken(): Promise<string | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Clear all stored credentials
|
|
48
|
+
*/
|
|
49
|
+
export declare function clearCredentials(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Check if user is authenticated
|
|
52
|
+
*/
|
|
53
|
+
export declare function isAuthenticated(): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Validate token expiry (basic check)
|
|
56
|
+
*/
|
|
57
|
+
export declare function isTokenExpired(expiry?: number): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Get authorization header for API requests
|
|
60
|
+
*/
|
|
61
|
+
export declare function getAuthHeader(): Promise<string | null>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadAuthConfig = loadAuthConfig;
|
|
37
|
+
exports.saveAuthConfig = saveAuthConfig;
|
|
38
|
+
exports.storeAccessToken = storeAccessToken;
|
|
39
|
+
exports.getAccessToken = getAccessToken;
|
|
40
|
+
exports.storeRefreshToken = storeRefreshToken;
|
|
41
|
+
exports.getRefreshToken = getRefreshToken;
|
|
42
|
+
exports.clearCredentials = clearCredentials;
|
|
43
|
+
exports.isAuthenticated = isAuthenticated;
|
|
44
|
+
exports.isTokenExpired = isTokenExpired;
|
|
45
|
+
exports.getAuthHeader = getAuthHeader;
|
|
46
|
+
const keytar = __importStar(require("keytar"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const SERVICE_NAME = 'blok0';
|
|
51
|
+
const CONFIG_DIR = path.join(os.homedir(), '.blok0');
|
|
52
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
53
|
+
/**
|
|
54
|
+
* Ensure config directory exists
|
|
55
|
+
*/
|
|
56
|
+
function ensureConfigDir() {
|
|
57
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
58
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load auth configuration from file
|
|
63
|
+
*/
|
|
64
|
+
function loadAuthConfig() {
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
67
|
+
const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
68
|
+
return JSON.parse(data);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.warn('Failed to load auth config:', error);
|
|
73
|
+
}
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Save auth configuration to file
|
|
78
|
+
*/
|
|
79
|
+
function saveAuthConfig(config) {
|
|
80
|
+
ensureConfigDir();
|
|
81
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Store access token securely
|
|
85
|
+
*/
|
|
86
|
+
async function storeAccessToken(token) {
|
|
87
|
+
try {
|
|
88
|
+
await keytar.setPassword(SERVICE_NAME, 'access_token', token);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('Failed to store access token:', error);
|
|
92
|
+
throw new Error('Unable to securely store access token');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get stored access token
|
|
97
|
+
*/
|
|
98
|
+
async function getAccessToken() {
|
|
99
|
+
try {
|
|
100
|
+
return await keytar.getPassword(SERVICE_NAME, 'access_token');
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error('Failed to retrieve access token:', error);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Store refresh token securely
|
|
109
|
+
*/
|
|
110
|
+
async function storeRefreshToken(token) {
|
|
111
|
+
try {
|
|
112
|
+
await keytar.setPassword(SERVICE_NAME, 'refresh_token', token);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('Failed to store refresh token:', error);
|
|
116
|
+
throw new Error('Unable to securely store refresh token');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get stored refresh token
|
|
121
|
+
*/
|
|
122
|
+
async function getRefreshToken() {
|
|
123
|
+
try {
|
|
124
|
+
return await keytar.getPassword(SERVICE_NAME, 'refresh_token');
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error('Failed to retrieve refresh token:', error);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Clear all stored credentials
|
|
133
|
+
*/
|
|
134
|
+
async function clearCredentials() {
|
|
135
|
+
try {
|
|
136
|
+
await keytar.deletePassword(SERVICE_NAME, 'access_token');
|
|
137
|
+
await keytar.deletePassword(SERVICE_NAME, 'refresh_token');
|
|
138
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
139
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('Failed to clear credentials:', error);
|
|
144
|
+
throw new Error('Unable to clear stored credentials');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if user is authenticated
|
|
149
|
+
*/
|
|
150
|
+
async function isAuthenticated() {
|
|
151
|
+
const token = await getAccessToken();
|
|
152
|
+
return token !== null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Validate token expiry (basic check)
|
|
156
|
+
*/
|
|
157
|
+
function isTokenExpired(expiry) {
|
|
158
|
+
if (!expiry)
|
|
159
|
+
return false;
|
|
160
|
+
return Date.now() >= expiry;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get authorization header for API requests
|
|
164
|
+
*/
|
|
165
|
+
async function getAuthHeader() {
|
|
166
|
+
const token = await getAccessToken();
|
|
167
|
+
return token ? `Bearer ${token}` : null;
|
|
168
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { AuthCallback, AuthServerOptions } from './index';
|
|
3
|
+
export declare class AuthServer extends EventEmitter {
|
|
4
|
+
private server?;
|
|
5
|
+
private port?;
|
|
6
|
+
private state;
|
|
7
|
+
private timeoutId?;
|
|
8
|
+
private resolveCallback?;
|
|
9
|
+
private rejectCallback?;
|
|
10
|
+
constructor(options?: AuthServerOptions);
|
|
11
|
+
/**
|
|
12
|
+
* Generate a random state parameter for CSRF protection
|
|
13
|
+
*/
|
|
14
|
+
private generateState;
|
|
15
|
+
/**
|
|
16
|
+
* Find an available port in the configured range
|
|
17
|
+
*/
|
|
18
|
+
private findAvailablePort;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a port is available
|
|
21
|
+
*/
|
|
22
|
+
private isPortAvailable;
|
|
23
|
+
/**
|
|
24
|
+
* Handle incoming HTTP requests
|
|
25
|
+
*/
|
|
26
|
+
private handleRequest;
|
|
27
|
+
/**
|
|
28
|
+
* Handle the OAuth callback
|
|
29
|
+
*/
|
|
30
|
+
private handleCallback;
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the server by finding an available port
|
|
33
|
+
*/
|
|
34
|
+
initialize(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Start the authentication server
|
|
37
|
+
*/
|
|
38
|
+
start(): Promise<AuthCallback>;
|
|
39
|
+
/**
|
|
40
|
+
* Handle authentication timeout
|
|
41
|
+
*/
|
|
42
|
+
private handleTimeout;
|
|
43
|
+
/**
|
|
44
|
+
* Get the authorization URL to open in browser
|
|
45
|
+
*/
|
|
46
|
+
getAuthorizationUrl(): string;
|
|
47
|
+
/**
|
|
48
|
+
* Clean up server resources
|
|
49
|
+
*/
|
|
50
|
+
private cleanup;
|
|
51
|
+
/**
|
|
52
|
+
* Stop the server
|
|
53
|
+
*/
|
|
54
|
+
stop(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AuthServer = void 0;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const events_1 = require("events");
|
|
39
|
+
const crypto_1 = require("crypto");
|
|
40
|
+
const constants_1 = require("./constants");
|
|
41
|
+
class AuthServer extends events_1.EventEmitter {
|
|
42
|
+
server;
|
|
43
|
+
port;
|
|
44
|
+
state;
|
|
45
|
+
timeoutId;
|
|
46
|
+
resolveCallback;
|
|
47
|
+
rejectCallback;
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
super();
|
|
50
|
+
this.state = options.state || this.generateState();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate a random state parameter for CSRF protection
|
|
54
|
+
*/
|
|
55
|
+
generateState() {
|
|
56
|
+
return (0, crypto_1.randomBytes)(32).toString('hex');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find an available port in the configured range
|
|
60
|
+
*/
|
|
61
|
+
async findAvailablePort() {
|
|
62
|
+
const { min, max } = constants_1.PORT_RANGE;
|
|
63
|
+
for (let port = min; port <= max; port++) {
|
|
64
|
+
if (await this.isPortAvailable(port)) {
|
|
65
|
+
return port;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw new Error('No available ports found in range');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a port is available
|
|
72
|
+
*/
|
|
73
|
+
async isPortAvailable(port) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const testServer = http.createServer();
|
|
76
|
+
testServer.listen(port, '127.0.0.1', () => {
|
|
77
|
+
testServer.close(() => resolve(true));
|
|
78
|
+
});
|
|
79
|
+
testServer.on('error', () => resolve(false));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Handle incoming HTTP requests
|
|
84
|
+
*/
|
|
85
|
+
handleRequest = (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const fullUrl = `http://localhost:${this.port}${req.url}`;
|
|
88
|
+
const parsedUrl = new URL(fullUrl);
|
|
89
|
+
const pathname = parsedUrl.pathname;
|
|
90
|
+
// Handle callback endpoint
|
|
91
|
+
if (pathname === constants_1.CALLBACK_PATH && req.method === 'GET') {
|
|
92
|
+
this.handleCallback(parsedUrl, res);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Handle any other request with a 404
|
|
96
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
97
|
+
res.end('Not found');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error('Error parsing request URL:', error);
|
|
101
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
102
|
+
res.end('Bad request');
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Handle the OAuth callback
|
|
107
|
+
*/
|
|
108
|
+
handleCallback(parsedUrl, res) {
|
|
109
|
+
const searchParams = parsedUrl.searchParams;
|
|
110
|
+
const state = searchParams.get('state');
|
|
111
|
+
const token = searchParams.get('token');
|
|
112
|
+
const error = searchParams.get('error');
|
|
113
|
+
// Validate state parameter
|
|
114
|
+
if (!state || state !== this.state) {
|
|
115
|
+
console.error('State parameter mismatch - possible CSRF attack');
|
|
116
|
+
console.error(`Expected: ${this.state}, Received: ${state}`);
|
|
117
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
118
|
+
res.end(constants_1.ERROR_HTML);
|
|
119
|
+
this.emit('error', new Error('Invalid state parameter'));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Handle error from authorization server
|
|
123
|
+
if (error) {
|
|
124
|
+
console.error('Authorization error:', error);
|
|
125
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
126
|
+
res.end(constants_1.ERROR_HTML);
|
|
127
|
+
this.emit('error', new Error(`Authorization failed: ${error}`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Handle successful authorization
|
|
131
|
+
if (token) {
|
|
132
|
+
const authCallback = { token };
|
|
133
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
134
|
+
res.end(constants_1.SUCCESS_HTML);
|
|
135
|
+
this.emit('success', authCallback);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Handle missing token
|
|
139
|
+
console.error('No token received in callback');
|
|
140
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
141
|
+
res.end(constants_1.ERROR_HTML);
|
|
142
|
+
this.emit('error', new Error('No token received'));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Initialize the server by finding an available port
|
|
146
|
+
*/
|
|
147
|
+
async initialize() {
|
|
148
|
+
if (!this.port) {
|
|
149
|
+
this.port = await this.findAvailablePort();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Start the authentication server
|
|
154
|
+
*/
|
|
155
|
+
async start() {
|
|
156
|
+
return new Promise(async (resolve, reject) => {
|
|
157
|
+
this.resolveCallback = resolve;
|
|
158
|
+
this.rejectCallback = reject;
|
|
159
|
+
try {
|
|
160
|
+
// Initialize (find port) if not already done
|
|
161
|
+
await this.initialize();
|
|
162
|
+
// Create server
|
|
163
|
+
this.server = http.createServer(this.handleRequest);
|
|
164
|
+
// Set up event listeners
|
|
165
|
+
this.on('success', (callback) => {
|
|
166
|
+
this.cleanup();
|
|
167
|
+
this.resolveCallback(callback);
|
|
168
|
+
});
|
|
169
|
+
this.on('error', (error) => {
|
|
170
|
+
this.cleanup();
|
|
171
|
+
this.rejectCallback(error);
|
|
172
|
+
});
|
|
173
|
+
// Set timeout
|
|
174
|
+
this.timeoutId = setTimeout(() => {
|
|
175
|
+
this.handleTimeout();
|
|
176
|
+
}, constants_1.DEFAULT_TIMEOUT);
|
|
177
|
+
// Start server
|
|
178
|
+
await new Promise((resolveServer, rejectServer) => {
|
|
179
|
+
this.server.listen(this.port, '127.0.0.1', () => {
|
|
180
|
+
console.log(`🔐 Authentication server started on http://localhost:${this.port}`);
|
|
181
|
+
resolveServer();
|
|
182
|
+
});
|
|
183
|
+
this.server.on('error', rejectServer);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
this.cleanup();
|
|
188
|
+
reject(error);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handle authentication timeout
|
|
194
|
+
*/
|
|
195
|
+
handleTimeout() {
|
|
196
|
+
console.log('⏱️ Authentication timed out');
|
|
197
|
+
// Send timeout page to any open browser windows
|
|
198
|
+
if (this.server) {
|
|
199
|
+
// Note: In a real implementation, we'd track active connections
|
|
200
|
+
// For now, we'll just emit the error
|
|
201
|
+
}
|
|
202
|
+
this.emit('error', new Error('Authentication timed out'));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get the authorization URL to open in browser
|
|
206
|
+
*/
|
|
207
|
+
getAuthorizationUrl() {
|
|
208
|
+
const redirectUri = `http://localhost:${this.port}${constants_1.CALLBACK_PATH}`;
|
|
209
|
+
const params = new URLSearchParams({
|
|
210
|
+
redirect_uri: redirectUri,
|
|
211
|
+
state: this.state,
|
|
212
|
+
});
|
|
213
|
+
return `${constants_1.AUTH_BASE_URL}${constants_1.AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Clean up server resources
|
|
217
|
+
*/
|
|
218
|
+
cleanup() {
|
|
219
|
+
if (this.timeoutId) {
|
|
220
|
+
clearTimeout(this.timeoutId);
|
|
221
|
+
this.timeoutId = undefined;
|
|
222
|
+
}
|
|
223
|
+
if (this.server) {
|
|
224
|
+
this.server.close();
|
|
225
|
+
this.server = undefined;
|
|
226
|
+
}
|
|
227
|
+
this.removeAllListeners();
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Stop the server
|
|
231
|
+
*/
|
|
232
|
+
stop() {
|
|
233
|
+
this.cleanup();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
exports.AuthServer = AuthServer;
|