A GraphQL server implementation using Yoga Server with TypeScript, PostgreSQL.
- User authentication & authorization
- JWT-based session management with refresh tokens
- Cookie-based security implementation
- Email notifications using Mailgun
- PostgreSQL database integration using Knex
- Custom GraphQL scalar types
- Input validation and sanitization
- Node.js (v14+)
- PostgreSQL database
- Mailgun account for emails
Create a .env file with the following:
NODE_ENV=development
APP_PORT=4000
DB_URL=postgresql://username:password@localhost:5432/dbname
JWT_SECRET=your_jwt_secret
COOKIE_DOMAIN=localhost
HASH_SECRET=your_hash_secret
MAILGUN_API_KEY=your_mailgun_key
MAILGUN_DOMAIN=your_mailgun_domain
CREATE_PASSWORD_URL=http://localhost:3000/create-password
USER_POOL_NAME=your_cognito_pool_name
USER_POOL_CLIENT_NAME=your_cognito_client_name
USER_POOL_CLIENT_ID=your_cognito_client_id
First, install dependencies:
npm installDevelopment:
npm run devBuild:
npm run buildStart production:
npm startType checking:
npm run type-checksrc/
├── dataApi/ # Database operations and data access layer
├── email/ # Email templates and email service
├── graphql/ # GraphQL schema, resolvers, and type definitions
├── lib/ # Core utilities and configurations
├── services/ # External services integration (Cognito, email)
├── typings/ # TypeScript type definitions
└── utils/ # Helper functions and utilities
GraphQL endpoint: http://localhost:4000/graphql
- Set up your PostgreSQL database
- Configure environment variables
- Run migrations (if any)
- Start development server
- Access GraphQL playground at http://localhost:4000/graphql
- CSRF protection enabled
- Secure cookie implementation
- JWT token refresh mechanism
- Input sanitization
- Role-based access control
Please follow the existing code style and add appropriate tests for new features.
To get started quickly:
1. Clone the repository
2. Run `npm install`
3. Set up your .env file with required variables
4. Set up PostgreSQL database
5. Run `npm run dev`
6. Visit http://localhost:4000/graphql
The server should be up and running with GraphQL playground available for testing queries and mutations.
what is the purpose of storing the refresh_token in the database
Storing refresh tokens in the database serves several important security purposes:
1. **Token Revocation**:
```typescript
// Ability to invalidate specific refresh tokens
async function revokeRefreshToken(userId: number, tokenToRevoke: string) {
const user = await dataApi.user.getUser({ id: userId });
const updatedTokens = user.refresh_tokens.filter(token => token !== tokenToRevoke);
await dataApi.user.updateUser({
id: userId,
user: { refresh_tokens: updatedTokens }
});
}
// Revoke all tokens (force logout everywhere)
async function revokeAllRefreshTokens(userId: number) {
await dataApi.user.updateUser({
id: userId,
user: { refresh_tokens: [] }
});
}
- Session Management:
// Track active sessions
async function getActiveSessions(userId: number) {
const user = await dataApi.user.getUser({ id: userId });
return user.refresh_tokens.length; // Number of active sessions
}
// Limit concurrent sessions
async function enforceSessionLimit(userId: number, maxSessions: number) {
const user = await dataApi.user.getUser({ id: userId });
if (user.refresh_tokens.length >= maxSessions) {
// Remove oldest token(s)
user.refresh_tokens = user.refresh_tokens.slice(-maxSessions + 1);
await dataApi.user.updateUser({
id: userId,
user: { refresh_tokens: user.refresh_tokens }
});
}
}- Security Audit:
// Add timestamp and device info to tokens
interface EnhancedRefreshToken {
token: string;
createdAt: Date;
deviceInfo: string;
lastUsed: Date;
}
// Update token usage
async function updateTokenUsage(userId: number, token: string) {
const user = await dataApi.user.getUser({ id: userId });
const tokens = user.refresh_tokens.map(t =>
t.token === token ? { ...t, lastUsed: new Date() } : t
);
await dataApi.user.updateUser({
id: userId,
user: { refresh_tokens: tokens }
});
}- Token Rotation:
async function rotateRefreshToken(oldToken: string) {
const user = await dataApi.user.getUserByRefreshToken({ refreshToken: oldToken });
if (!user) return null;
// Generate new token
const newToken = uuidv4();
// Remove old token and add new one
const tokens = user.refresh_tokens.filter(t => t !== oldToken);
tokens.push(newToken);
await dataApi.user.updateUser({
id: user.id,
user: { refresh_tokens: tokens }
});
return newToken;
}- Security Breach Handling:
// Detect and handle suspicious activity
async function handleSecurityBreach(userId: number) {
// Immediately invalidate all refresh tokens
await dataApi.user.updateUser({
id: userId,
user: {
refresh_tokens: [],
securityLockout: true,
lastSecurityEvent: new Date()
}
});
// Notify user
await sendSecurityAlert(userId);
}- Token Cleanup:
// Periodic cleanup of unused tokens
async function cleanupOldTokens() {
const users = await dataApi.user.getUsers({
filter: [['refresh_tokens', '!=', '[]']]
});
for (const user of users.items) {
// Filter out tokens older than X days
const activeTokens = user.refresh_tokens.filter(token => {
const tokenAge = Date.now() - token.createdAt;
return tokenAge < (30 * 24 * 60 * 60 * 1000); // 30 days
});
if (activeTokens.length !== user.refresh_tokens.length) {
await dataApi.user.updateUser({
id: user.id,
user: { refresh_tokens: activeTokens }
});
}
}
}Implementation Considerations:
interface RefreshTokenRecord {
token: string;
createdAt: Date;
lastUsed?: Date;
deviceInfo?: string;
ip?: string;
}
// When creating new token
const newToken: RefreshTokenRecord = {
token: uuidv4(),
createdAt: new Date(),
deviceInfo: request.headers['user-agent'],
ip: request.ip
};
// Store as JSON in database
const tokens = [...user.refresh_tokens, newToken];
await dataApi.user.updateUser({
id: user.id,
user: { refresh_tokens: tokens }
});Benefits:
- Ability to revoke access
- Track active sessions
- Implement security policies
- Audit trail for security events
- Token rotation for enhanced security
- Protection against token theft
This approach provides:
- Better security control
- User session management
- Audit capabilities
- Breach response mechanisms
- Token lifecycle management