deevoauth 1.4.5
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 +222 -0
- package/app/api/internal/create-user/route.js +54 -0
- package/app/api/internal/developer/clients/route.js +122 -0
- package/app/api/internal/generate-code/route.js +41 -0
- package/app/api/oauth/token/route.js +115 -0
- package/app/api/oauth/userinfo/route.js +46 -0
- package/app/consent/page.jsx +202 -0
- package/app/dashboard/page.jsx +254 -0
- package/app/developers/page.jsx +287 -0
- package/app/globals.css +1041 -0
- package/app/layout.jsx +33 -0
- package/app/login/page.jsx +257 -0
- package/app/page.jsx +165 -0
- package/app/register/page.jsx +249 -0
- package/components/DeevoLogo.jsx +41 -0
- package/firebase.json +10 -0
- package/jsconfig.json +7 -0
- package/lib/auth-context.jsx +102 -0
- package/lib/firebase-admin.js +32 -0
- package/lib/firebase.js +18 -0
- package/next.config.mjs +9 -0
- package/package.json +20 -0
- package/public/deevo-logo.svg +3 -0
- package/sdk/README.md +216 -0
- package/sdk/build.js +30 -0
- package/sdk/deevo-oauth-1.4.5.tgz +0 -0
- package/sdk/dist/index.d.ts +69 -0
- package/sdk/dist/index.js +228 -0
- package/sdk/dist/index.mjs +222 -0
- package/sdk/package.json +39 -0
- package/sdk/src/index.d.ts +69 -0
- package/sdk/src/index.js +228 -0
package/sdk/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# deevo-auth
|
|
2
|
+
|
|
3
|
+
Official SDK for integrating **Deevo Account** OAuth 2.0 authentication into your applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install deevo-auth
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Get your credentials
|
|
14
|
+
|
|
15
|
+
Visit the [Deevo Developer Console](https://deevo.tech/developers) to register your application and get your `clientId` and `clientSecret`.
|
|
16
|
+
|
|
17
|
+
### 2. Configure the SDK
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
const { DeevoAuth } = require('deevo-auth');
|
|
21
|
+
// or: import { DeevoAuth } from 'deevo-auth';
|
|
22
|
+
|
|
23
|
+
const deevo = new DeevoAuth({
|
|
24
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
25
|
+
clientSecret: 'YOUR_CLIENT_SECRET',
|
|
26
|
+
redirectUri: 'https://yourapp.com/auth/callback',
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Redirect users to sign in
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
// Express.js example
|
|
34
|
+
app.get('/auth/login', (req, res) => {
|
|
35
|
+
const loginUrl = deevo.getAuthUrl();
|
|
36
|
+
res.redirect(loginUrl);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 4. Handle the callback
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
app.get('/auth/callback', async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const { accessToken, user } = await deevo.handleCallback(req.query.code);
|
|
46
|
+
|
|
47
|
+
// user = { sub: 'uid', name: 'John', email: 'john@example.com', picture: '...' }
|
|
48
|
+
req.session.user = user;
|
|
49
|
+
req.session.accessToken = accessToken;
|
|
50
|
+
|
|
51
|
+
res.redirect('/dashboard');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Auth failed:', error);
|
|
54
|
+
res.redirect('/login?error=auth_failed');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API Reference
|
|
60
|
+
|
|
61
|
+
### `new DeevoAuth(config)`
|
|
62
|
+
|
|
63
|
+
| Parameter | Type | Required | Description |
|
|
64
|
+
|-----------|------|----------|-------------|
|
|
65
|
+
| `clientId` | string | ✅ | OAuth client ID from Developer Console |
|
|
66
|
+
| `clientSecret` | string | ✅ | OAuth client secret (keep server-side!) |
|
|
67
|
+
| `redirectUri` | string | ✅ | Registered callback URL |
|
|
68
|
+
| `authServerUrl` | string | ❌ | Override auth server (default: `https://deevo.tech`) |
|
|
69
|
+
| `scope` | string | ❌ | Scopes (default: `'profile email'`) |
|
|
70
|
+
|
|
71
|
+
### `deevo.getAuthUrl(options?)`
|
|
72
|
+
|
|
73
|
+
Returns the URL to redirect users to for authentication.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const url = deevo.getAuthUrl({ state: 'csrf-token' });
|
|
77
|
+
// => https://deevo.tech/login?client_id=...&redirect_uri=...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `deevo.exchangeCode(code)`
|
|
81
|
+
|
|
82
|
+
Exchanges an authorization code for tokens.
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const tokens = await deevo.exchangeCode(code);
|
|
86
|
+
// => { access_token: '...', token_type: 'Bearer', expires_in: 3600 }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `deevo.getUserInfo(accessToken)`
|
|
90
|
+
|
|
91
|
+
Fetches the authenticated user's profile.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const user = await deevo.getUserInfo(tokens.access_token);
|
|
95
|
+
// => { sub: 'uid', name: 'John Doe', email: 'john@example.com', picture: '...' }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `deevo.handleCallback(code)`
|
|
99
|
+
|
|
100
|
+
Convenience method: exchanges code AND fetches user info in one call.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const { accessToken, user } = await deevo.handleCallback(code);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `deevo.verifyToken(accessToken)`
|
|
107
|
+
|
|
108
|
+
Verifies a token and returns user info. Useful for API middleware.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
const user = await deevo.verifyToken(req.headers.authorization.split(' ')[1]);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Express Middleware
|
|
115
|
+
|
|
116
|
+
Protect your API routes:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const { DeevoAuth, deevoMiddleware } = require('deevo-auth');
|
|
120
|
+
|
|
121
|
+
const deevo = new DeevoAuth({ /* config */ });
|
|
122
|
+
|
|
123
|
+
app.get('/api/profile', deevoMiddleware(deevo), (req, res) => {
|
|
124
|
+
// req.deevoUser contains the authenticated user's profile
|
|
125
|
+
res.json({ user: req.deevoUser });
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Next.js Integration
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// pages/api/auth/login.js
|
|
133
|
+
import { DeevoAuth } from 'deevo-auth';
|
|
134
|
+
|
|
135
|
+
const deevo = new DeevoAuth({
|
|
136
|
+
clientId: process.env.DEEVO_CLIENT_ID,
|
|
137
|
+
clientSecret: process.env.DEEVO_CLIENT_SECRET,
|
|
138
|
+
redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback`,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export default function handler(req, res) {
|
|
142
|
+
res.redirect(deevo.getAuthUrl());
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// pages/api/auth/callback.js
|
|
148
|
+
export default async function handler(req, res) {
|
|
149
|
+
const { code } = req.query;
|
|
150
|
+
const { accessToken, user } = await deevo.handleCallback(code);
|
|
151
|
+
|
|
152
|
+
// Set session cookie, JWT, etc.
|
|
153
|
+
res.redirect('/dashboard');
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## React Component (Client-side)
|
|
158
|
+
|
|
159
|
+
```jsx
|
|
160
|
+
function LoginButton() {
|
|
161
|
+
const handleLogin = () => {
|
|
162
|
+
window.location.href = '/api/auth/login';
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<button onClick={handleLogin}>
|
|
167
|
+
Sign in with Deevo
|
|
168
|
+
</button>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Error Handling
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const { DeevoAuthError } = require('deevo-auth');
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const { user } = await deevo.handleCallback(code);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error instanceof DeevoAuthError) {
|
|
182
|
+
console.error(`Deevo Auth Error [${error.code}]:`, error.message);
|
|
183
|
+
// error.code: 'invalid_grant', 'invalid_client', 'token_exchange_failed', etc.
|
|
184
|
+
// error.statusCode: HTTP status code
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## OAuth Flow Diagram
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
Your App Deevo Auth Server
|
|
193
|
+
| |
|
|
194
|
+
| 1. Redirect to /login |
|
|
195
|
+
| ---------------------------→ |
|
|
196
|
+
| | 2. User signs in
|
|
197
|
+
| | (Google or Email)
|
|
198
|
+
| 3. Redirect back with code |
|
|
199
|
+
| ←--------------------------- |
|
|
200
|
+
| |
|
|
201
|
+
| 4. POST /api/oauth/token |
|
|
202
|
+
| ---------------------------→ |
|
|
203
|
+
| |
|
|
204
|
+
| 5. Returns access_token |
|
|
205
|
+
| ←--------------------------- |
|
|
206
|
+
| |
|
|
207
|
+
| 6. GET /api/oauth/userinfo |
|
|
208
|
+
| ---------------------------→ |
|
|
209
|
+
| |
|
|
210
|
+
| 7. Returns user profile |
|
|
211
|
+
| ←--------------------------- |
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT © [Deevo](https://deevo.tech)
|
package/sdk/build.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Simple build: copy src to dist with CJS and ESM variants
|
|
5
|
+
const src = fs.readFileSync(path.join(__dirname, 'src', 'index.js'), 'utf8');
|
|
6
|
+
|
|
7
|
+
// Create dist directory
|
|
8
|
+
const distDir = path.join(__dirname, 'dist');
|
|
9
|
+
if (!fs.existsSync(distDir)) {
|
|
10
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// CJS version (remove ESM export lines)
|
|
14
|
+
const cjs = src
|
|
15
|
+
.replace(/^export \{.*\};?$/gm, '')
|
|
16
|
+
.replace(/^export default.*$/gm, '');
|
|
17
|
+
|
|
18
|
+
fs.writeFileSync(path.join(distDir, 'index.js'), cjs);
|
|
19
|
+
|
|
20
|
+
// ESM version (remove CJS export lines)
|
|
21
|
+
const esm = src
|
|
22
|
+
.replace(/if \(typeof module.*\n(.*\n)*?.*module\.exports\.default.*\n\}/m, '');
|
|
23
|
+
|
|
24
|
+
fs.writeFileSync(path.join(distDir, 'index.mjs'), esm);
|
|
25
|
+
|
|
26
|
+
// Copy type definitions
|
|
27
|
+
const types = fs.readFileSync(path.join(__dirname, 'src', 'index.d.ts'), 'utf8');
|
|
28
|
+
fs.writeFileSync(path.join(distDir, 'index.d.ts'), types);
|
|
29
|
+
|
|
30
|
+
console.log('✓ Built deevo-auth SDK to dist/');
|
|
Binary file
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface DeevoAuthConfig {
|
|
2
|
+
/** Your OAuth client ID from the Deevo Developer Console */
|
|
3
|
+
clientId: string;
|
|
4
|
+
/** Your OAuth client secret (keep server-side only!) */
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
/** The callback URL registered in your Deevo app */
|
|
7
|
+
redirectUri: string;
|
|
8
|
+
/** Override the auth server URL (default: https://deevo.tech) */
|
|
9
|
+
authServerUrl?: string;
|
|
10
|
+
/** Space-separated scopes (default: 'profile email') */
|
|
11
|
+
scope?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TokenResponse {
|
|
15
|
+
access_token: string;
|
|
16
|
+
token_type: string;
|
|
17
|
+
expires_in: number;
|
|
18
|
+
scope?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DeevoUser {
|
|
22
|
+
/** Unique user identifier */
|
|
23
|
+
sub: string;
|
|
24
|
+
/** User's full name */
|
|
25
|
+
name: string;
|
|
26
|
+
/** User's email address */
|
|
27
|
+
email: string;
|
|
28
|
+
/** URL to user's profile picture */
|
|
29
|
+
picture: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CallbackResult {
|
|
33
|
+
accessToken: string;
|
|
34
|
+
tokenType: string;
|
|
35
|
+
expiresIn: number;
|
|
36
|
+
user: DeevoUser;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class DeevoAuth {
|
|
40
|
+
constructor(config: DeevoAuthConfig);
|
|
41
|
+
|
|
42
|
+
/** Get the URL to redirect users to for authentication */
|
|
43
|
+
getAuthUrl(options?: { state?: string }): string;
|
|
44
|
+
|
|
45
|
+
/** Exchange an authorization code for tokens */
|
|
46
|
+
exchangeCode(code: string): Promise<TokenResponse>;
|
|
47
|
+
|
|
48
|
+
/** Get the authenticated user's profile information */
|
|
49
|
+
getUserInfo(accessToken: string): Promise<DeevoUser>;
|
|
50
|
+
|
|
51
|
+
/** Complete OAuth flow: exchange code + get user info */
|
|
52
|
+
handleCallback(code: string): Promise<CallbackResult>;
|
|
53
|
+
|
|
54
|
+
/** Verify an access token and get user info */
|
|
55
|
+
verifyToken(accessToken: string): Promise<DeevoUser>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class DeevoAuthError extends Error {
|
|
59
|
+
code: string;
|
|
60
|
+
statusCode: number;
|
|
61
|
+
constructor(message: string, code: string, statusCode: number);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Express.js middleware for protecting routes */
|
|
65
|
+
export function deevoMiddleware(
|
|
66
|
+
deevoAuth: DeevoAuth
|
|
67
|
+
): (req: any, res: any, next: any) => Promise<void>;
|
|
68
|
+
|
|
69
|
+
export default DeevoAuth;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deevo Auth SDK
|
|
3
|
+
* Official SDK for integrating Deevo Account OAuth 2.0 into your applications.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { DeevoAuth } from 'deevo-auth';
|
|
7
|
+
*
|
|
8
|
+
* const deevo = new DeevoAuth({
|
|
9
|
+
* clientId: 'YOUR_CLIENT_ID',
|
|
10
|
+
* clientSecret: 'YOUR_CLIENT_SECRET',
|
|
11
|
+
* redirectUri: 'https://yourapp.com/auth/callback',
|
|
12
|
+
* });
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const DEFAULT_AUTH_URL = 'https://deevo.tech';
|
|
16
|
+
|
|
17
|
+
class DeevoAuth {
|
|
18
|
+
/**
|
|
19
|
+
* Create a new DeevoAuth instance.
|
|
20
|
+
* @param {Object} config
|
|
21
|
+
* @param {string} config.clientId - Your OAuth client ID from the Deevo Developer Console
|
|
22
|
+
* @param {string} config.clientSecret - Your OAuth client secret (keep this server-side only!)
|
|
23
|
+
* @param {string} config.redirectUri - The callback URL registered in your Deevo app
|
|
24
|
+
* @param {string} [config.authServerUrl] - Override the auth server URL (default: https://deevo.tech)
|
|
25
|
+
* @param {string} [config.scope] - Space-separated scopes (default: 'profile email')
|
|
26
|
+
*/
|
|
27
|
+
constructor(config) {
|
|
28
|
+
if (!config.clientId) throw new Error('DeevoAuth: clientId is required');
|
|
29
|
+
if (!config.clientSecret) throw new Error('DeevoAuth: clientSecret is required');
|
|
30
|
+
if (!config.redirectUri) throw new Error('DeevoAuth: redirectUri is required');
|
|
31
|
+
|
|
32
|
+
this.clientId = config.clientId;
|
|
33
|
+
this.clientSecret = config.clientSecret;
|
|
34
|
+
this.redirectUri = config.redirectUri;
|
|
35
|
+
this.authServerUrl = (config.authServerUrl || DEFAULT_AUTH_URL).replace(/\/$/, '');
|
|
36
|
+
this.scope = config.scope || 'profile email';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the URL to redirect users to for authentication.
|
|
41
|
+
* Redirect the user's browser to this URL to start the OAuth flow.
|
|
42
|
+
*
|
|
43
|
+
* @param {Object} [options]
|
|
44
|
+
* @param {string} [options.state] - Optional state parameter for CSRF protection
|
|
45
|
+
* @returns {string} The authorization URL
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Express.js route
|
|
49
|
+
* app.get('/auth/login', (req, res) => {
|
|
50
|
+
* const url = deevo.getAuthUrl({ state: 'random-csrf-token' });
|
|
51
|
+
* res.redirect(url);
|
|
52
|
+
* });
|
|
53
|
+
*/
|
|
54
|
+
getAuthUrl(options = {}) {
|
|
55
|
+
const params = new URLSearchParams({
|
|
56
|
+
client_id: this.clientId,
|
|
57
|
+
redirect_uri: this.redirectUri,
|
|
58
|
+
scope: this.scope,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (options.state) {
|
|
62
|
+
params.set('state', options.state);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return `${this.authServerUrl}/login?${params.toString()}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Exchange an authorization code for an access token.
|
|
70
|
+
* Call this in your callback route after the user is redirected back.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} code - The authorization code from the callback URL query params
|
|
73
|
+
* @returns {Promise<Object>} Token response with access_token, token_type, expires_in
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Express.js callback route
|
|
77
|
+
* app.get('/auth/callback', async (req, res) => {
|
|
78
|
+
* const { code } = req.query;
|
|
79
|
+
* const tokens = await deevo.exchangeCode(code);
|
|
80
|
+
* // tokens.access_token is your bearer token
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
async exchangeCode(code) {
|
|
84
|
+
const response = await fetch(`${this.authServerUrl}/api/oauth/token`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
grant_type: 'authorization_code',
|
|
89
|
+
code,
|
|
90
|
+
client_id: this.clientId,
|
|
91
|
+
client_secret: this.clientSecret,
|
|
92
|
+
redirect_uri: this.redirectUri,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const error = await response.json().catch(() => ({}));
|
|
98
|
+
throw new DeevoAuthError(
|
|
99
|
+
error.message || `Token exchange failed with status ${response.status}`,
|
|
100
|
+
error.error || 'token_exchange_failed',
|
|
101
|
+
response.status
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return response.json();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the authenticated user's profile information.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} accessToken - The access token from exchangeCode()
|
|
112
|
+
* @returns {Promise<Object>} User profile { sub, name, email, picture }
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* const user = await deevo.getUserInfo(tokens.access_token);
|
|
116
|
+
* console.log(user.email, user.name);
|
|
117
|
+
*/
|
|
118
|
+
async getUserInfo(accessToken) {
|
|
119
|
+
const response = await fetch(`${this.authServerUrl}/api/oauth/userinfo`, {
|
|
120
|
+
headers: {
|
|
121
|
+
Authorization: `Bearer ${accessToken}`,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const error = await response.json().catch(() => ({}));
|
|
127
|
+
throw new DeevoAuthError(
|
|
128
|
+
error.message || `UserInfo request failed with status ${response.status}`,
|
|
129
|
+
error.error || 'userinfo_failed',
|
|
130
|
+
response.status
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return response.json();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Complete OAuth flow in one call: exchange code and get user info.
|
|
139
|
+
* Convenience method combining exchangeCode() + getUserInfo().
|
|
140
|
+
*
|
|
141
|
+
* @param {string} code - The authorization code from the callback URL
|
|
142
|
+
* @returns {Promise<Object>} { accessToken, tokenType, expiresIn, user }
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* app.get('/auth/callback', async (req, res) => {
|
|
146
|
+
* const { accessToken, user } = await deevo.handleCallback(req.query.code);
|
|
147
|
+
* req.session.user = user;
|
|
148
|
+
* req.session.accessToken = accessToken;
|
|
149
|
+
* res.redirect('/dashboard');
|
|
150
|
+
* });
|
|
151
|
+
*/
|
|
152
|
+
async handleCallback(code) {
|
|
153
|
+
const tokens = await this.exchangeCode(code);
|
|
154
|
+
const user = await this.getUserInfo(tokens.access_token);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
accessToken: tokens.access_token,
|
|
158
|
+
tokenType: tokens.token_type,
|
|
159
|
+
expiresIn: tokens.expires_in,
|
|
160
|
+
user,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Verify an access token and get user info (useful for API middleware).
|
|
166
|
+
*
|
|
167
|
+
* @param {string} accessToken - The Bearer token to verify
|
|
168
|
+
* @returns {Promise<Object>} User profile if valid
|
|
169
|
+
* @throws {DeevoAuthError} If the token is invalid or expired
|
|
170
|
+
*/
|
|
171
|
+
async verifyToken(accessToken) {
|
|
172
|
+
return this.getUserInfo(accessToken);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Express.js middleware for protecting routes with Deevo Auth.
|
|
178
|
+
*
|
|
179
|
+
* @param {DeevoAuth} deevoAuth - A configured DeevoAuth instance
|
|
180
|
+
* @returns {Function} Express middleware
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* app.get('/api/protected', deevoMiddleware(deevo), (req, res) => {
|
|
184
|
+
* res.json({ user: req.deevoUser });
|
|
185
|
+
* });
|
|
186
|
+
*/
|
|
187
|
+
function deevoMiddleware(deevoAuth) {
|
|
188
|
+
return async (req, res, next) => {
|
|
189
|
+
const authHeader = req.headers.authorization;
|
|
190
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
191
|
+
return res.status(401).json({ error: 'Missing or invalid authorization header' });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const token = authHeader.split(' ')[1];
|
|
195
|
+
try {
|
|
196
|
+
const user = await deevoAuth.verifyToken(token);
|
|
197
|
+
req.deevoUser = user;
|
|
198
|
+
next();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Custom error class for Deevo Auth errors.
|
|
207
|
+
*/
|
|
208
|
+
class DeevoAuthError extends Error {
|
|
209
|
+
constructor(message, code, statusCode) {
|
|
210
|
+
super(message);
|
|
211
|
+
this.name = 'DeevoAuthError';
|
|
212
|
+
this.code = code;
|
|
213
|
+
this.statusCode = statusCode;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Export for CommonJS
|
|
218
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
219
|
+
module.exports = { DeevoAuth, deevoMiddleware, DeevoAuthError };
|
|
220
|
+
module.exports.DeevoAuth = DeevoAuth;
|
|
221
|
+
module.exports.deevoMiddleware = deevoMiddleware;
|
|
222
|
+
module.exports.DeevoAuthError = DeevoAuthError;
|
|
223
|
+
module.exports.default = DeevoAuth;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Export for ESM
|
|
227
|
+
|
|
228
|
+
|