@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 CHANGED
@@ -4,44 +4,54 @@ Official Node.js SDK for TSP Scale Authentication.
4
4
 
5
5
  🚀 Quick Start (Zero-Config)
6
6
  -----------------------------
7
- Verify TSP Scale JWT access tokens securely from your backend server.
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
- You can easily protect your Express or Next.js routes using the built-in middleware or by manually verifying tokens.
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 { tspAuth } = require('@tspscale/npm-login');
20
+ const { TSPClient } = require('@tspscale/npm-login');
22
21
 
23
22
  const app = express();
24
23
 
25
- // Protect a route - requires a Bearer token in the Authorization header
26
- app.get('/api/protected', tspAuth.middleware(), (req, res) => {
27
- // The middleware automatically attaches the user profile to the request
28
- res.json({ message: "Success!", user: req.user });
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
- ### Manual Token Verification
33
- If you are using Next.js or a custom server, you can manually verify a token:
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
- const { tspAuth } = require('@tspscale/npm-login');
36
-
37
- async function checkToken(accessToken) {
38
- const result = await tspAuth.verifyToken(accessToken);
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
- constructor(options?: {
15
- apiUrl?: string;
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 the frontend to send the token in the Authorization header: `Bearer <token>`.
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 the frontend to send the token in the Authorization header: `Bearer <token>`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tspscale/npm-login",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
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?: { apiUrl?: string }) {
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 the frontend to send the token in the Authorization header: `Bearer <token>`.
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();