Skip to content

Commit 274215a

Browse files
authored
Merge pull request #11 from bitriel/feature/auth
Integration KOOMPI OAuth Digital ID
2 parents 680f384 + 718d7c0 commit 274215a

File tree

31 files changed

+2936
-799
lines changed

31 files changed

+2936
-799
lines changed

apps/backend/.env.example

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
# Copy to .env and provide strong values before running in production
2+
3+
# Server Configuration
24
PORT=4000
3-
JWT_SECRET=replace-with-secure-random-string
5+
6+
# JWT Configuration
7+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
48
JWT_EXPIRES_IN=15m
59

6-
KOOMPI_CLIENT_ID=your-client-id
7-
KOOMPI_CLIENT_SECRET=your-client-secret
8-
KOOMPI_REDIRECT_URI=http://localhost:4000/api/oauth/callback
10+
# MongoDB Configuration
11+
MONGODB_URI=mongodb://localhost:27017/bitriel
912

10-
MONGODB_URI=mongodb://127.0.0.1:27017/bitriel?authSource=admin&readPreference=primary&directConnection=true&ssl=false
13+
# Koompi OAuth Configuration
14+
KOOMPI_CLIENT_ID=pk_xxxxxxxxxxxxxxxxxxxxxx
15+
KOOMPI_CLIENT_SECRET=sk_xxxxxxxxxxxxxxxxxxxxxx
16+
KOOMPI_REDIRECT_URI=http://localhost:4000/api/oauth/callback
17+
KOOMPI_MOBILE_REDIRECT_URI=http://localhost:4000/api/oauth/callback?platform=mobile
1118

1219
# Frontend Configuration
1320
FRONTEND_URL=http://localhost:5173
1421
FRONTEND_CALLBACK_PATH=/oauth/callback
22+
23+
24+

apps/backend/README.md

Lines changed: 166 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,172 @@
1-
# @bitriel/backend
1+
# Bitriel Backend API
22

3-
Simple Express API with JWT auth helpers.
3+
Node.js + Express + TypeScript backend API for Bitriel digital wallet application with Koompi OAuth integration.
44

5-
## Getting started
5+
## Features
66

7-
1. Copy `.env.example` to `.env` and set a strong `JWT_SECRET`.
8-
2. Install dependencies from the repo root: `pnpm install`.
9-
3. Start the API: `pnpm --filter @bitriel/backend dev`.
7+
- 🔐 Koompi OAuth 2.0 + PKCE authentication
8+
- 🎫 JWT token-based authorization
9+
- 👤 User profile management
10+
- 🗄️ MongoDB database integration
11+
- 📱 Separate endpoints for web and mobile platforms
12+
- 🛡️ TypeScript for type safety
13+
- 🏗️ Clean architecture with service layer
1014

11-
## Routes
15+
## Prerequisites
1216

13-
- `GET /health` – readiness probe.
14-
- `POST /auth/token` – provide `{ "userId": "<id>", "roles": ["admin"] }` to receive a signed JWT.
15-
- `GET /me` – attach `Authorization: Bearer <token>` to inspect the decoded claims.
17+
- Node.js 18+ and pnpm 9+
18+
- MongoDB 5+ (local or cloud)
19+
- Koompi OAuth credentials (client ID & secret)
1620

17-
`JWT_EXPIRES_IN` (default `15m`) controls token lifetime.
21+
## Quick Start
22+
23+
### 1. Install Dependencies
24+
25+
```bash
26+
cd apps/backend
27+
pnpm install
28+
```
29+
30+
### 2. Environment Configuration
31+
32+
Copy `.env.example` to `.env`:
33+
34+
```bash
35+
cp .env.example .env
36+
```
37+
38+
Edit `.env` with your configuration:
39+
40+
```env
41+
# Server
42+
PORT=4000
43+
44+
# JWT
45+
JWT_SECRET=your-super-secret-jwt-key-change-this
46+
JWT_EXPIRES_IN=7d
47+
48+
# MongoDB
49+
MONGODB_URI=mongodb://localhost:27017/bitriel
50+
51+
# Koompi OAuth
52+
KOOMPI_CLIENT_ID=your-koompi-client-id
53+
KOOMPI_CLIENT_SECRET=your-koompi-client-secret
54+
KOOMPI_REDIRECT_URI=http://localhost:4000/api/oauth/callback
55+
KOOMPI_MOBILE_REDIRECT_URI=http://localhost:4000/api/oauth/callback?platform=mobile
56+
57+
# Frontend
58+
FRONTEND_URL=http://localhost:3000
59+
FRONTEND_CALLBACK_PATH=/oauth/callback
60+
```
61+
62+
### 3. Start MongoDB
63+
64+
```bash
65+
# Using Docker
66+
docker run -d -p 27017:27017 --name mongodb mongo:latest
67+
68+
# Or use MongoDB Atlas (cloud)
69+
# Update MONGODB_URI in .env with your Atlas connection string
70+
```
71+
72+
### 4. Run Development Server
73+
74+
```bash
75+
pnpm dev
76+
```
77+
78+
Server will start at `http://localhost:4000`
79+
80+
## API Endpoints
81+
82+
### Authentication
83+
84+
| Endpoint | Method | Description |
85+
|----------|--------|-------------|
86+
| `/api/oauth/login` | GET | Initiates OAuth flow (query: `?platform=mobile\|web`) |
87+
| `/api/oauth/callback` | GET | OAuth callback (handles both web and mobile via `?platform=`) |
88+
| `/api/auth/me` | GET | Get authenticated user profile (requires JWT) |
89+
90+
### Testing Endpoints
91+
92+
```bash
93+
# Test OAuth login (redirects to Koompi)
94+
curl http://localhost:4000/api/oauth/login?platform=mobile
95+
96+
# Test user profile (requires valid JWT)
97+
curl http://localhost:4000/api/auth/me \
98+
-H "Authorization: Bearer YOUR_JWT_TOKEN"
99+
```
100+
101+
## Project Structure
102+
103+
```
104+
apps/backend/src/
105+
├── controllers/ # HTTP request handlers
106+
│ ├── oauthController.ts
107+
│ └── userController.ts
108+
├── services/ # Business logic layer
109+
│ ├── oauthService.ts
110+
│ └── userService.ts
111+
├── models/ # MongoDB schemas
112+
│ └── User.ts
113+
├── middleware/ # Express middleware
114+
│ └── auth.ts
115+
├── routes/ # API route definitions
116+
│ ├── oauthRoutes.ts
117+
│ └── userRoutes.ts
118+
├── types/ # TypeScript types
119+
│ └── oauth.ts
120+
├── utils/ # Utility functions
121+
│ └── jwt.ts
122+
├── config.ts # Environment config
123+
└── index.ts # App entry point
124+
```
125+
126+
## Development
127+
128+
### Available Scripts
129+
130+
```bash
131+
pnpm dev # Start development server with hot reload
132+
pnpm build # Build for production
133+
pnpm start # Start production server
134+
pnpm lint # Run ESLint
135+
```
136+
137+
## Documentation
138+
139+
- **Full OAuth Integration Guide:** See [KOOMPI_OAUTH_INTEGRATION.md](../../docs/KOOMPI_OAUTH_INTEGRATION.md)
140+
- **API Documentation:** See [API Endpoints](#api-endpoints) above
141+
142+
## Troubleshooting
143+
144+
### Common Issues
145+
146+
1. **MongoDB Connection Failed**
147+
- Check MongoDB is running: `docker ps`
148+
- Verify `MONGODB_URI` in `.env`
149+
150+
2. **OAuth Redirect Mismatch**
151+
- Verify redirect URIs in Koompi OAuth dashboard
152+
- Ensure exact match with `.env` values
153+
154+
3. **Invalid JWT Token**
155+
- Check `JWT_SECRET` is set
156+
- Verify token format: `Bearer <token>`
157+
158+
## Security
159+
160+
- ✅ Never commit `.env` files
161+
- ✅ Use strong JWT secrets (32+ characters)
162+
- ✅ Enable HTTPS in production
163+
- ✅ Validate all user inputs
164+
165+
## Support
166+
167+
For detailed integration guide and troubleshooting:
168+
- See [docs/KOOMPI_OAUTH_INTEGRATION.md](../../docs/KOOMPI_OAUTH_INTEGRATION.md)
169+
170+
---
171+
172+
**Last Updated:** 2025-01-21

apps/backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"dependencies": {
1313
"@bitriel/http-client": "workspace:*",
1414
"@koompi/oauth": "^1.0.4",
15+
"@types/cookie-parser": "^1.4.10",
16+
"cookie-parser": "^1.4.7",
1517
"cors": "^2.8.5",
1618
"dotenv": "^16.4.5",
1719
"express": "^4.21.2",

apps/backend/src/app.ts

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,40 @@
1-
import cors from 'cors'
2-
import express from 'express'
3-
import jwt from 'jsonwebtoken'
4-
import morgan from 'morgan'
5-
import { config } from './config.js'
6-
import { authenticate, type AuthenticatedRequest } from './middleware/authenticate.js'
7-
import oauthRoutes from './routes/oauthRoutes.js'
8-
import userRoutes from './routes/userRoutes.js'
9-
10-
const app = express()
11-
12-
app.use(cors())
13-
app.use(express.json())
14-
app.use(express.urlencoded({ extended: true }))
15-
app.use(morgan('dev'))
1+
import cors from 'cors';
2+
import express from 'express';
3+
import jwt from 'jsonwebtoken';
4+
import morgan from 'morgan';
5+
import { config } from './config.js';
6+
import oauthRoutes from './routes/oauthRoutes.js';
7+
import userRoutes from './routes/userRoutes.js';
8+
9+
const app = express();
10+
11+
app.use(cors());
12+
app.use(express.json());
13+
app.use(express.urlencoded({ extended: true }));
14+
app.use(morgan('dev'));
1615

1716
app.get('/health', (_req, res) => {
18-
res.json({ status: 'ok', timestamp: new Date().toISOString() })
19-
})
17+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
18+
});
2019

2120
app.post('/auth/token', (req, res) => {
22-
const { userId, roles = [] } = req.body as { userId?: string; roles?: string[] }
21+
const { userId, roles = [] } = req.body as { userId?: string; roles?: string[] };
2322

2423
if (!userId) {
25-
return res.status(400).json({ error: 'userId is required' })
24+
return res.status(400).json({ error: 'userId is required' });
2625
}
2726

2827
const token = jwt.sign({ sub: userId, roles }, config.jwt.secret, {
2928
expiresIn: config.jwt.expiresIn,
30-
})
31-
32-
return res.json({ token, expiresIn: config.jwt.expiresIn })
33-
})
34-
35-
app.get('/me', authenticate, (req, res) => {
36-
const { user } = req as AuthenticatedRequest
29+
});
3730

38-
return res.json({
39-
userId: user?.sub,
40-
claims: user,
41-
})
42-
})
31+
return res.json({ token, expiresIn: config.jwt.expiresIn });
32+
});
4333

4434
// OAuth routes
45-
app.use('/api/oauth', oauthRoutes)
35+
app.use('/api/oauth', oauthRoutes);
4636

4737
// User routes
48-
app.use('/api/auth', userRoutes)
38+
app.use('/api/auth', userRoutes);
4939

50-
export default app
40+
export default app;

apps/backend/src/config.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import type { SignOptions } from 'jsonwebtoken'
1+
import type { SignOptions } from 'jsonwebtoken';
22

33
const ensure = (value: string | undefined, key: string) => {
44
if (!value) {
5-
throw new Error(`Missing required environment variable: ${key}`)
5+
throw new Error(`Missing required environment variable: ${key}`);
66
}
7-
return value
8-
}
7+
return value;
8+
};
99

10-
const jwtSecret = ensure(process.env.JWT_SECRET, 'JWT_SECRET')
11-
const jwtExpiresIn = (process.env.JWT_EXPIRES_IN ?? '15m') as SignOptions['expiresIn']
10+
const jwtSecret = ensure(process.env.JWT_SECRET, 'JWT_SECRET');
11+
const jwtExpiresIn = (process.env.JWT_EXPIRES_IN ?? '15m') as SignOptions['expiresIn'];
1212

1313
export const config = {
1414
server: {
@@ -25,11 +25,12 @@ export const config = {
2525
clientId: ensure(process.env.KOOMPI_CLIENT_ID, 'KOOMPI_CLIENT_ID'),
2626
clientSecret: ensure(process.env.KOOMPI_CLIENT_SECRET, 'KOOMPI_CLIENT_SECRET'),
2727
redirectUri: process.env.KOOMPI_REDIRECT_URI || 'http://localhost:4000/api/oauth/callback',
28+
mobileRedirectUri: process.env.KOOMPI_MOBILE_REDIRECT_URI || 'http://localhost:4000/api/oauth/callback?platform=mobile',
2829
},
2930
frontend: {
3031
url: process.env.FRONTEND_URL || 'http://localhost:5173',
3132
callbackPath: process.env.FRONTEND_CALLBACK_PATH || '/oauth/callback',
3233
},
33-
}
34+
};
3435

35-
export type JwtConfig = typeof config.jwt
36+
export type JwtConfig = typeof config.jwt;

0 commit comments

Comments
 (0)