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.
@@ -0,0 +1,222 @@
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
+
219
+
220
+ // Export for ESM
221
+ export { DeevoAuth, deevoMiddleware, DeevoAuthError };
222
+ export default DeevoAuth;
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "deevoauth",
3
+ "version": "1.4.6",
4
+ "description": "Official SDK for Deevo Account OAuth 2.0 authentication. Add 'Sign in with Deevo' to your app.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "node build.js",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "deevo",
25
+ "auth",
26
+ "oauth",
27
+ "oauth2",
28
+ "authentication",
29
+ "login",
30
+ "identity"
31
+ ],
32
+ "author": "Deevo",
33
+ "license": "MIT",
34
+ "homepage": "https://deevo.tech",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/deevo-tech/deevo-auth"
38
+ }
39
+ }
@@ -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
+ export { DeevoAuth, deevoMiddleware, DeevoAuthError };
228
+ export default DeevoAuth;