Authentication & API Keys
๐ Authentication & Configurationโ
Authentication Method: API Key (Master + Subuser API Keys)
Required Environment Variables:
| Variable | Description | Required |
|---|---|---|
SENDGRID_API_KEY | Master Sendgrid API key | โ |
SENDGRID_DEFAULT_DOMAIN_ID | Default domain ID for subusers | โ |
SENDGRID_EVENT_WEBHOOK_URL | Webhook URL for email events | โ |
SENDGRID_INBOUND_WEBHOOK_URL | Webhook URL for inbound emails | โ |
CONVERSATION_SOCKET | Conversation socket service URL | โ |
APP_SECRET | JWT secret for socket authentication | โ |
Example Configuration:
SENDGRID_API_KEY=SG.abc123...
SENDGRID_DEFAULT_DOMAIN_ID=12345678
SENDGRID_EVENT_WEBHOOK_URL=https://api.dashclicks.com/v1/integrations/sendgrid/webhook
SENDGRID_INBOUND_WEBHOOK_URL=https://api.dashclicks.com/v1/integrations/sendgrid/webhook/inbound
CONVERSATION_SOCKET=http://localhost:6001
APP_SECRET=your_jwt_secret
Credential Storage: Master API key in environment, subuser API keys in integrations-sendgrid-keys collection
๐ API Key Managementโ
Get API Keyโ
Endpoint: GET /v1/integrations/sendgrid/api-key
Purpose: Retrieve existing Sendgrid API key for an account
Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "507f1f77bcf86cd799439011",
"account_id": "507f191e810c19729de860ea",
"api_key": "SG.xyz123..."
}
}
Add API Keyโ
Endpoint: POST /v1/integrations/sendgrid/api-key
Purpose: Generate new Sendgrid API key for a subuser
Process:
-
Get subuser from
integrations-sendgrid-subusers -
Check if API key already exists (return 403 if exists)
-
Create API key via Sendgrid API:
POST https://api.sendgrid.com/v3/api_keys
Authorization: Bearer {SENDGRID_API_KEY}
On-Behalf-Of: {subuser_username}
{
"name": "Dashboard Access"
} -
Store in
integrations-sendgrid-keyscollection -
Create webhook for subuser via
WebHookModel.createWebhook()
Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "507f1f77bcf86cd799439011",
"account_id": "507f191e810c19729de860ea",
"api_key": "SG.xyz123..."
}
}
Error Scenarios:
// API key already exists
{
"success": false,
"errno": 403,
"message": "Sendgrid Api Key already exist"
}
// No subuser found
{
"success": false,
"errno": 403,
"message": "No subuser associated with this account"
}
```## ๐ก๏ธ Middleware
### getSendgridApiKey
**Purpose**: Validate and attach Sendgrid API key to request
**Location**: `Middleware/getSendgridApiKey.js`
**Usage**:
```javascript
router.post('/mail', verifyAuthorization(), getSendgridApiKey, (req, res) => {
// req.sendgrid_api_key available here
});
Logic:
- Extract
account_idfromreq.auth - Query
SendgridCollection.getApiKey({ account_id }) - If not found:
{
"success": false,
"errno": 403,
"message": "Sendgrid api key is misconfigured.",
"error": "SENDGRID_API_MISCONFIGURED"
} - If found: Attach
req.sendgrid_api_key - Call
next()
Implementation:
const getSendgridApiKey = async (req, res, next) => {
try {
const { account_id } = req.auth;
const { isEmpty, doc } = await SendgridCollection.getApiKey({ account_id });
if (isEmpty) {
throw {
message: 'Sendgrid api key is misconfigured.',
error: 'SENDGRID_API_MISCONFIGURED',
errno: 403,
};
}
req.sendgrid_api_key = doc.api_key;
next();
} catch (error) {
res.status(error.errno || 500).json({
success: false,
errno: error.errno || 500,
message: error.message,
error: error.error,
});
}
};
๐ฆ SendgridCollection Modelโ
Purpose: API key management in database
Methods:
getApiKey() Methodโ
SendgridCollection.getApiKey({ account_id });
// Returns: { isEmpty: boolean, doc: { id, account_id, api_key } }
Query:
db.getCollection('integrations.sendgrid.keys').findOne({
account_id: ObjectId(account_id),
});
addApiKey() Methodโ
SendgridCollection.addApiKey({
account_id,
api_key,
});
// Returns: { id, account_id, api_key }
Creates Document:
{
_id: new ObjectId(),
account_id: ObjectId(account_id),
api_key: "SG.xyz123..."
}
๐๏ธ ApiKeyControllerLocation: Controllers/ApiKeyController.jsโ
getApiKey() Controllerโ
Route: GET /v1/integrations/sendgrid/auth
Logic:
- Extract
account_idfromreq.auth - Call
SendgridCollection.getApiKey({ account_id }) - Return API key document
Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "507f1f77bcf86cd799439011",
"account_id": "507f191e810c19729de860ea",
"api_key": "SG.abc123..."
}
}
Error:
{
"success": false,
"errno": 400,
"message": "Record Not found"
}
addApiKey()โ
Route: POST /v1/integrations/sendgrid/auth
Logic:
-
Get subuser from account document
-
Check if API key already exists:
const existingKey = await SendgridCollection.getApiKey({ account_id });
if (!existingKey.isEmpty) {
throw { message: 'Sendgrid Api Key already exist', errno: 403 };
} -
Create API key via Sendgrid API (on-behalf-of subuser):
const response = await this.client.request({
method: 'POST',
url: '/v3/api_keys',
body: {
name: 'Dashboard Access',
},
headers: {
'on-behalf-of': subuser.username,
},
}); -
Store in database:
const apiKey = await SendgridCollection.addApiKey({
account_id: account_id,
api_key: response.body.api_key,
}); -
Create webhook:
await WebHookModel.createWebhook({
account_id,
api_key: response.body.api_key,
});
Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "507f1f77bcf86cd799439011",
"account_id": "507f191e810c19729de860ea",
"api_key": "SG.xyz123..."
}
}
๐ Related Routesโ
Route Configuration (Routes/apikey.js):
const router = require('express').Router();
const ApiKeyController = require('../Controllers/ApiKeyController');
const { verifyAuthorization } = require('../../../Middleware/verifyAuthorization');
router.get('/', verifyAuthorization(), ApiKeyController.getApiKey);
router.post('/', verifyAuthorization(), ApiKeyController.addApiKey);
module.exports = router;
Mounted in index.js:
app.use('/auth', require('./Routes/apikey'));
๐จ Error Handlingโ
Common Error Scenarios:
| Error | Scenario | HTTP Status | Handling |
|---|---|---|---|
| 403 | No API key found | 403 | User must create API key first |
| 403 | API key already exists | 403 | Return error message |
| 403 | No subuser associated | 403 | Must create subuser first |
| 403 | API key misconfigured | 403 | Middleware blocks request |
| 500 | Sendgrid API call failed | 500 | Return Sendgrid error message |
| 400 | Invalid account_id in token | 400 | JWT verification failed |
๐งช Testingโ
Test API Key Retrievalโ
curl -X GET http://localhost:5003/v1/e/sendgrid/auth \
-H "Authorization: Bearer {jwt_token}"
Expected Response (if exists):
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "507f1f77bcf86cd799439011",
"account_id": "507f191e810c19729de860ea",
"api_key": "SG.xyz123..."
}
}
Test API Key Creationโ
curl -X POST http://localhost:5003/v1/e/sendgrid/auth \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json"
Expected Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "507f1f77bcf86cd799439011",
"account_id": "507f191e810c19729de860ea",
"api_key": "SG.xyz123..."
}
}
Test Middlewareโ
// Test that middleware blocks requests without API key
const account = await Account.create({
sendgrid: {
subuser: { username: 'test_subuser' },
},
});
// No API key in database
const response = await request(app)
.post('/v1/e/sendgrid/mail')
.set('Authorization', `Bearer ${jwtToken}`)
.send({ subject: 'Test' });
expect(response.status).toBe(403);
expect(response.body.error).toBe('SENDGRID_API_MISCONFIGURED');
โ ๏ธ Important Notesโ
- ๐ Master vs Subuser Keys: Master key in environment only, subuser keys in database
- ๐ก๏ธ Middleware Protection: All email-sending routes protected by
getSendgridApiKey - ๐ซ Duplicate Prevention: Checks for existing API key before creating new one
- ๐ Webhook Auto-Setup: Creating API key automatically configures event webhook
- ๐ One Key Per Account: Each DashClicks account gets exactly one Sendgrid API key
- ๐ Key Rotation: No automatic rotation - must delete and recreate manually
- ๐ฅ Subuser Requirement: Cannot create API key without existing subuser
- ๐ Name Convention: All API keys created with name "Dashboard Access"