@tspscale/npm-login 1.0.1 → 1.0.4
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 +34 -24
- package/dist/index.d.ts +40 -6
- package/dist/index.js +82 -7
- package/package.json +1 -1
- package/src/index.ts +97 -15
package/README.md
CHANGED
|
@@ -4,44 +4,54 @@ Official Node.js SDK for TSP Scale Authentication.
|
|
|
4
4
|
|
|
5
5
|
🚀 Quick Start (Zero-Config)
|
|
6
6
|
-----------------------------
|
|
7
|
-
|
|
7
|
+
Add "Login with TSP Scale" to your server-side Node.js applications in minutes!
|
|
8
8
|
|
|
9
9
|
### Installation
|
|
10
10
|
```bash
|
|
11
11
|
npm install @tspscale/npm-login
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
🛠️ Usage
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
🛠️ Usage: Full OAuth Setup (Express)
|
|
15
|
+
--------------------------------------
|
|
16
|
+
If you are building a traditional server-side web app without a frontend framework like React, you can use the SDK's built-in Express handlers to do the heavy lifting for you!
|
|
17
17
|
|
|
18
|
-
### Express Middleware
|
|
19
18
|
```javascript
|
|
20
19
|
const express = require('express');
|
|
21
|
-
const {
|
|
20
|
+
const { TSPClient } = require('@tspscale/npm-login');
|
|
22
21
|
|
|
23
22
|
const app = express();
|
|
24
23
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// 1. Initialize the Client
|
|
25
|
+
const tspAuth = new TSPClient({
|
|
26
|
+
clientId: process.env.TSP_CLIENT_ID,
|
|
27
|
+
clientSecret: process.env.TSP_CLIENT_SECRET,
|
|
28
|
+
redirectUri: 'http://localhost:3000/auth/callback'
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
// 2. Login Route (Automatically redirects to TSP Scale)
|
|
32
|
+
app.get('/auth/login', tspAuth.loginHandler());
|
|
33
|
+
|
|
34
|
+
// 3. Callback Route (Automatically exchanges the code for a token)
|
|
35
|
+
app.get('/auth/callback', tspAuth.callbackHandler({
|
|
36
|
+
onSuccess: (token, req, res) => {
|
|
37
|
+
// Save the token (e.g. in a cookie) and redirect to the dashboard
|
|
38
|
+
res.cookie('auth_token', token);
|
|
39
|
+
res.redirect('/dashboard');
|
|
40
|
+
},
|
|
41
|
+
onError: (error, req, res) => {
|
|
42
|
+
res.status(500).send(`Login failed: ${error}`);
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
30
45
|
```
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
🔐 Protecting API Routes
|
|
48
|
+
------------------------
|
|
49
|
+
If your frontend has already acquired an access token (e.g., via the React SDK), you can use the middleware to protect your backend routes.
|
|
50
|
+
|
|
34
51
|
```javascript
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (result.error) {
|
|
41
|
-
console.error("Verification failed:", result.error);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log("User Profile:", result.user);
|
|
46
|
-
}
|
|
52
|
+
// Protect a route - requires a Bearer token in the Authorization header
|
|
53
|
+
app.get('/api/protected', tspAuth.middleware(), (req, res) => {
|
|
54
|
+
// The middleware automatically verifies the token and attaches the user!
|
|
55
|
+
res.json({ message: "Success!", user: req.user });
|
|
56
|
+
});
|
|
47
57
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -8,24 +8,58 @@ export interface TSPUser {
|
|
|
8
8
|
export interface TSPAuthResult {
|
|
9
9
|
user?: TSPUser;
|
|
10
10
|
error?: string;
|
|
11
|
+
accessToken?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TSPClientConfig {
|
|
14
|
+
clientId?: string;
|
|
15
|
+
clientSecret?: string;
|
|
16
|
+
redirectUri?: string;
|
|
17
|
+
apiUrl?: string;
|
|
18
|
+
authUrl?: string;
|
|
11
19
|
}
|
|
12
20
|
export declare class TSPClient {
|
|
13
21
|
private apiUrl;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
private authUrl;
|
|
23
|
+
private clientId?;
|
|
24
|
+
private clientSecret?;
|
|
25
|
+
private redirectUri?;
|
|
26
|
+
constructor(options?: TSPClientConfig);
|
|
27
|
+
/**
|
|
28
|
+
* Generates the OAuth 2.0 Authorization URL to redirect the user to.
|
|
29
|
+
*/
|
|
30
|
+
getAuthorizationUrl(state?: string, scope?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Securely exchanges an authorization code for an access token.
|
|
33
|
+
*/
|
|
34
|
+
exchangeToken(code: string): Promise<{
|
|
35
|
+
access_token?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
}>;
|
|
17
38
|
/**
|
|
18
39
|
* Verifies an access token and returns the user profile.
|
|
19
|
-
* @param accessToken The OAuth 2.0 Access Token provided by the frontend.
|
|
20
40
|
*/
|
|
21
41
|
verifyToken(accessToken: string): Promise<TSPAuthResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Express Route Handler: Automatically redirects the user to TSP Scale to log in.
|
|
44
|
+
*/
|
|
45
|
+
loginHandler(options?: {
|
|
46
|
+
state?: string;
|
|
47
|
+
scope?: string;
|
|
48
|
+
}): (req: any, res: any) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Express Route Handler: Intercepts the callback, exchanges the code, and provides the token.
|
|
51
|
+
*/
|
|
52
|
+
callbackHandler(options: {
|
|
53
|
+
onSuccess: (token: string, req: any, res: any) => void;
|
|
54
|
+
onError: (error: string, req: any, res: any) => void;
|
|
55
|
+
}): (req: any, res: any) => Promise<void>;
|
|
22
56
|
/**
|
|
23
57
|
* Express middleware to protect routes.
|
|
24
|
-
* Requires
|
|
58
|
+
* Requires a Bearer token in the Authorization header.
|
|
25
59
|
*/
|
|
26
60
|
middleware(): (req: any, res: any, next: any) => Promise<any>;
|
|
27
61
|
}
|
|
28
62
|
/**
|
|
29
|
-
* Convenience default client instance
|
|
63
|
+
* Convenience default client instance (must be configured before use for full OAuth flow)
|
|
30
64
|
*/
|
|
31
65
|
export declare const tspAuth: TSPClient;
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.tspAuth = exports.TSPClient = void 0;
|
|
4
4
|
class TSPClient {
|
|
5
5
|
constructor(options) {
|
|
6
|
-
this.apiUrl = options?.apiUrl || 'https://tspscale.in/api/v1';
|
|
6
|
+
this.apiUrl = options?.apiUrl || 'https://api.tspscale.in/api/v1';
|
|
7
|
+
this.authUrl = options?.authUrl || 'https://tspscale.in/oauth/authorize';
|
|
8
|
+
this.clientId = options?.clientId;
|
|
9
|
+
this.clientSecret = options?.clientSecret;
|
|
10
|
+
this.redirectUri = options?.redirectUri;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generates the OAuth 2.0 Authorization URL to redirect the user to.
|
|
14
|
+
*/
|
|
15
|
+
getAuthorizationUrl(state = 'tsp_auth', scope = 'read') {
|
|
16
|
+
if (!this.clientId || !this.redirectUri) {
|
|
17
|
+
throw new Error('TSPClient requires clientId and redirectUri to generate an authorization URL.');
|
|
18
|
+
}
|
|
19
|
+
const params = new URLSearchParams({
|
|
20
|
+
client_id: this.clientId,
|
|
21
|
+
redirect_uri: this.redirectUri,
|
|
22
|
+
response_type: 'code',
|
|
23
|
+
scope: scope,
|
|
24
|
+
state: state
|
|
25
|
+
});
|
|
26
|
+
return `${this.authUrl}?${params.toString()}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Securely exchanges an authorization code for an access token.
|
|
30
|
+
*/
|
|
31
|
+
async exchangeToken(code) {
|
|
32
|
+
if (!this.clientId || !this.clientSecret || !this.redirectUri) {
|
|
33
|
+
throw new Error('TSPClient requires clientId, clientSecret, and redirectUri to exchange tokens.');
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`${this.apiUrl}/oauth/token`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
grant_type: 'authorization_code',
|
|
41
|
+
client_id: this.clientId,
|
|
42
|
+
client_secret: this.clientSecret,
|
|
43
|
+
code: code,
|
|
44
|
+
redirect_uri: this.redirectUri
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
const data = await response.json();
|
|
48
|
+
if (!response.ok)
|
|
49
|
+
return { error: data.error || 'Failed to exchange token' };
|
|
50
|
+
return { access_token: data.access_token };
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return { error: err.message || 'Network error exchanging token' };
|
|
54
|
+
}
|
|
7
55
|
}
|
|
8
56
|
/**
|
|
9
57
|
* Verifies an access token and returns the user profile.
|
|
10
|
-
* @param accessToken The OAuth 2.0 Access Token provided by the frontend.
|
|
11
58
|
*/
|
|
12
59
|
async verifyToken(accessToken) {
|
|
13
60
|
try {
|
|
@@ -19,18 +66,47 @@ class TSPClient {
|
|
|
19
66
|
}
|
|
20
67
|
});
|
|
21
68
|
const data = await response.json();
|
|
22
|
-
if (!response.ok)
|
|
69
|
+
if (!response.ok)
|
|
23
70
|
return { error: data.error || 'Failed to verify token' };
|
|
24
|
-
}
|
|
25
71
|
return { user: data };
|
|
26
72
|
}
|
|
27
73
|
catch (err) {
|
|
28
74
|
return { error: err.message || 'Network error verifying token' };
|
|
29
75
|
}
|
|
30
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Express Route Handler: Automatically redirects the user to TSP Scale to log in.
|
|
79
|
+
*/
|
|
80
|
+
loginHandler(options) {
|
|
81
|
+
return (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
const url = this.getAuthorizationUrl(options?.state, options?.scope);
|
|
84
|
+
res.redirect(url);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
res.status(500).send(`TSP SDK Error: ${err.message}`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Express Route Handler: Intercepts the callback, exchanges the code, and provides the token.
|
|
93
|
+
*/
|
|
94
|
+
callbackHandler(options) {
|
|
95
|
+
return async (req, res) => {
|
|
96
|
+
const code = req.query.code;
|
|
97
|
+
if (!code) {
|
|
98
|
+
return options.onError('No authorization code provided in the callback', req, res);
|
|
99
|
+
}
|
|
100
|
+
const tokenResult = await this.exchangeToken(code);
|
|
101
|
+
if (tokenResult.error || !tokenResult.access_token) {
|
|
102
|
+
return options.onError(tokenResult.error || 'Token exchange failed', req, res);
|
|
103
|
+
}
|
|
104
|
+
return options.onSuccess(tokenResult.access_token, req, res);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
31
107
|
/**
|
|
32
108
|
* Express middleware to protect routes.
|
|
33
|
-
* Requires
|
|
109
|
+
* Requires a Bearer token in the Authorization header.
|
|
34
110
|
*/
|
|
35
111
|
middleware() {
|
|
36
112
|
return async (req, res, next) => {
|
|
@@ -43,7 +119,6 @@ class TSPClient {
|
|
|
43
119
|
if (result.error || !result.user) {
|
|
44
120
|
return res.status(401).json({ error: result.error || 'Unauthorized' });
|
|
45
121
|
}
|
|
46
|
-
// Attach user to the request object
|
|
47
122
|
req.user = result.user;
|
|
48
123
|
next();
|
|
49
124
|
};
|
|
@@ -51,6 +126,6 @@ class TSPClient {
|
|
|
51
126
|
}
|
|
52
127
|
exports.TSPClient = TSPClient;
|
|
53
128
|
/**
|
|
54
|
-
* Convenience default client instance
|
|
129
|
+
* Convenience default client instance (must be configured before use for full OAuth flow)
|
|
55
130
|
*/
|
|
56
131
|
exports.tspAuth = new TSPClient();
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -9,18 +9,78 @@ export interface TSPUser {
|
|
|
9
9
|
export interface TSPAuthResult {
|
|
10
10
|
user?: TSPUser;
|
|
11
11
|
error?: string;
|
|
12
|
+
accessToken?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TSPClientConfig {
|
|
16
|
+
clientId?: string;
|
|
17
|
+
clientSecret?: string;
|
|
18
|
+
redirectUri?: string;
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
authUrl?: string;
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
export class TSPClient {
|
|
15
24
|
private apiUrl: string;
|
|
25
|
+
private authUrl: string;
|
|
26
|
+
private clientId?: string;
|
|
27
|
+
private clientSecret?: string;
|
|
28
|
+
private redirectUri?: string;
|
|
16
29
|
|
|
17
|
-
constructor(options?:
|
|
18
|
-
this.apiUrl = options?.apiUrl || 'https://tspscale.in/api/v1';
|
|
30
|
+
constructor(options?: TSPClientConfig) {
|
|
31
|
+
this.apiUrl = options?.apiUrl || 'https://api.tspscale.in/api/v1';
|
|
32
|
+
this.authUrl = options?.authUrl || 'https://tspscale.in/oauth/authorize';
|
|
33
|
+
this.clientId = options?.clientId;
|
|
34
|
+
this.clientSecret = options?.clientSecret;
|
|
35
|
+
this.redirectUri = options?.redirectUri;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates the OAuth 2.0 Authorization URL to redirect the user to.
|
|
40
|
+
*/
|
|
41
|
+
getAuthorizationUrl(state: string = 'tsp_auth', scope: string = 'read'): string {
|
|
42
|
+
if (!this.clientId || !this.redirectUri) {
|
|
43
|
+
throw new Error('TSPClient requires clientId and redirectUri to generate an authorization URL.');
|
|
44
|
+
}
|
|
45
|
+
const params = new URLSearchParams({
|
|
46
|
+
client_id: this.clientId,
|
|
47
|
+
redirect_uri: this.redirectUri,
|
|
48
|
+
response_type: 'code',
|
|
49
|
+
scope: scope,
|
|
50
|
+
state: state
|
|
51
|
+
});
|
|
52
|
+
return `${this.authUrl}?${params.toString()}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Securely exchanges an authorization code for an access token.
|
|
57
|
+
*/
|
|
58
|
+
async exchangeToken(code: string): Promise<{ access_token?: string, error?: string }> {
|
|
59
|
+
if (!this.clientId || !this.clientSecret || !this.redirectUri) {
|
|
60
|
+
throw new Error('TSPClient requires clientId, clientSecret, and redirectUri to exchange tokens.');
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(`${this.apiUrl}/oauth/token`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
grant_type: 'authorization_code',
|
|
68
|
+
client_id: this.clientId,
|
|
69
|
+
client_secret: this.clientSecret,
|
|
70
|
+
code: code,
|
|
71
|
+
redirect_uri: this.redirectUri
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
if (!response.ok) return { error: data.error || 'Failed to exchange token' };
|
|
76
|
+
return { access_token: data.access_token };
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
return { error: err.message || 'Network error exchanging token' };
|
|
79
|
+
}
|
|
19
80
|
}
|
|
20
81
|
|
|
21
82
|
/**
|
|
22
83
|
* Verifies an access token and returns the user profile.
|
|
23
|
-
* @param accessToken The OAuth 2.0 Access Token provided by the frontend.
|
|
24
84
|
*/
|
|
25
85
|
async verifyToken(accessToken: string): Promise<TSPAuthResult> {
|
|
26
86
|
try {
|
|
@@ -31,22 +91,48 @@ export class TSPClient {
|
|
|
31
91
|
'Content-Type': 'application/json'
|
|
32
92
|
}
|
|
33
93
|
});
|
|
34
|
-
|
|
35
94
|
const data = await response.json();
|
|
36
|
-
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
return { error: data.error || 'Failed to verify token' };
|
|
39
|
-
}
|
|
40
|
-
|
|
95
|
+
if (!response.ok) return { error: data.error || 'Failed to verify token' };
|
|
41
96
|
return { user: data };
|
|
42
97
|
} catch (err: any) {
|
|
43
98
|
return { error: err.message || 'Network error verifying token' };
|
|
44
99
|
}
|
|
45
100
|
}
|
|
46
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Express Route Handler: Automatically redirects the user to TSP Scale to log in.
|
|
104
|
+
*/
|
|
105
|
+
loginHandler(options?: { state?: string, scope?: string }) {
|
|
106
|
+
return (req: any, res: any) => {
|
|
107
|
+
try {
|
|
108
|
+
const url = this.getAuthorizationUrl(options?.state, options?.scope);
|
|
109
|
+
res.redirect(url);
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
res.status(500).send(`TSP SDK Error: ${err.message}`);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Express Route Handler: Intercepts the callback, exchanges the code, and provides the token.
|
|
118
|
+
*/
|
|
119
|
+
callbackHandler(options: { onSuccess: (token: string, req: any, res: any) => void, onError: (error: string, req: any, res: any) => void }) {
|
|
120
|
+
return async (req: any, res: any) => {
|
|
121
|
+
const code = req.query.code as string;
|
|
122
|
+
if (!code) {
|
|
123
|
+
return options.onError('No authorization code provided in the callback', req, res);
|
|
124
|
+
}
|
|
125
|
+
const tokenResult = await this.exchangeToken(code);
|
|
126
|
+
if (tokenResult.error || !tokenResult.access_token) {
|
|
127
|
+
return options.onError(tokenResult.error || 'Token exchange failed', req, res);
|
|
128
|
+
}
|
|
129
|
+
return options.onSuccess(tokenResult.access_token, req, res);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
47
133
|
/**
|
|
48
134
|
* Express middleware to protect routes.
|
|
49
|
-
* Requires
|
|
135
|
+
* Requires a Bearer token in the Authorization header.
|
|
50
136
|
*/
|
|
51
137
|
middleware() {
|
|
52
138
|
return async (req: any, res: any, next: any) => {
|
|
@@ -54,15 +140,11 @@ export class TSPClient {
|
|
|
54
140
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
55
141
|
return res.status(401).json({ error: 'Missing or invalid Bearer token' });
|
|
56
142
|
}
|
|
57
|
-
|
|
58
143
|
const token = authHeader.split(' ')[1];
|
|
59
144
|
const result = await this.verifyToken(token);
|
|
60
|
-
|
|
61
145
|
if (result.error || !result.user) {
|
|
62
146
|
return res.status(401).json({ error: result.error || 'Unauthorized' });
|
|
63
147
|
}
|
|
64
|
-
|
|
65
|
-
// Attach user to the request object
|
|
66
148
|
req.user = result.user;
|
|
67
149
|
next();
|
|
68
150
|
};
|
|
@@ -70,6 +152,6 @@ export class TSPClient {
|
|
|
70
152
|
}
|
|
71
153
|
|
|
72
154
|
/**
|
|
73
|
-
* Convenience default client instance
|
|
155
|
+
* Convenience default client instance (must be configured before use for full OAuth flow)
|
|
74
156
|
*/
|
|
75
157
|
export const tspAuth = new TSPClient();
|