427 lines
11 KiB
Markdown
427 lines
11 KiB
Markdown
# FastCheck Backend Implementation Guide
|
|
|
|
## QR Code Authentication Flow
|
|
|
|
### Overview
|
|
The frontend displays a QR code that contains a session ID. When a user scans this QR code with the mobile app, the mobile app authenticates and links to that session. The frontend polls the backend every 2 seconds to check if the session has been authenticated.
|
|
|
|
### Step-by-Step Implementation
|
|
|
|
---
|
|
|
|
## 1. Create WebSession (QR Code Generation)
|
|
|
|
### Frontend Request:
|
|
```typescript
|
|
GET https://api.fastcheck.store/websession
|
|
Headers: {
|
|
"Content-Type": "application/json"
|
|
}
|
|
```
|
|
|
|
### Backend Response:
|
|
```json
|
|
{
|
|
"sessionId": "1AF3781BF6B94604B771AEA1D44FA63A",
|
|
"userId": "",
|
|
"expires": "2026-01-19T10:50:00Z",
|
|
"userSessionId": "",
|
|
"Status": false
|
|
}
|
|
```
|
|
|
|
### Backend Implementation:
|
|
```javascript
|
|
// Example Node.js/Express
|
|
app.get('/websession', (req, res) => {
|
|
// Generate unique session ID (UUID or similar)
|
|
const sessionId = generateUUID(); // e.g., "1AF3781BF6B94604B771AEA1D44FA63A"
|
|
|
|
// Set expiration time (e.g., 5 minutes from now)
|
|
const expires = new Date(Date.now() + 5 * 60 * 1000).toISOString();
|
|
|
|
// Store session in database or cache (Redis recommended)
|
|
await sessionStore.create({
|
|
sessionId: sessionId,
|
|
userId: null,
|
|
userSessionId: null,
|
|
status: false,
|
|
expiresAt: expires,
|
|
createdAt: new Date()
|
|
});
|
|
|
|
// Return session data
|
|
res.json({
|
|
sessionId: sessionId,
|
|
userId: "",
|
|
expires: expires,
|
|
userSessionId: "",
|
|
Status: false
|
|
});
|
|
});
|
|
```
|
|
|
|
### What Frontend Does:
|
|
```typescript
|
|
// Frontend generates QR code data from session ID
|
|
const qrData = `fastcheck://login?session=${sessionId}`;
|
|
// Example: "fastcheck://login?session=1AF3781BF6B94604B771AEA1D44FA63A"
|
|
```
|
|
|
|
**QR Code Contains:** Deep link URL with session ID
|
|
- Format: `fastcheck://login?session={sessionId}`
|
|
- Mobile app will parse this URL and extract the sessionId
|
|
- Mobile app will then authenticate and update this session
|
|
|
|
---
|
|
|
|
## 2. Check WebSession Status (Polling)
|
|
|
|
### Frontend Request (Every 2 seconds):
|
|
```typescript
|
|
GET https://api.fastcheck.store/websession/1AF3781BF6B94604B771AEA1D44FA63A
|
|
Headers: {
|
|
"Content-Type": "application/json"
|
|
}
|
|
```
|
|
|
|
### Backend Response (Not Authenticated Yet):
|
|
```json
|
|
{
|
|
"sessionId": "1AF3781BF6B94604B771AEA1D44FA63A",
|
|
"userId": "",
|
|
"expires": "2026-01-19T10:50:00Z",
|
|
"userSessionId": "",
|
|
"Status": false
|
|
}
|
|
```
|
|
|
|
### Backend Response (Authenticated):
|
|
```json
|
|
{
|
|
"sessionId": "1AF3781BF6B94604B771AEA1D44FA63A",
|
|
"userId": "kHaAe9roaC2uq63AKGE/8+Ti/t/iFro68QhEZ1dRGLo",
|
|
"expires": "2026-01-19T12:00:00Z",
|
|
"userSessionId": "8A94EFEFD003426A9B456C48CAC99BE6",
|
|
"Status": true
|
|
}
|
|
```
|
|
|
|
### Backend Implementation:
|
|
```javascript
|
|
app.get('/websession/:sessionId', async (req, res) => {
|
|
const { sessionId } = req.params;
|
|
|
|
// Retrieve session from database/cache
|
|
const session = await sessionStore.get(sessionId);
|
|
|
|
if (!session) {
|
|
return res.status(404).json({ message: "Session not found" });
|
|
}
|
|
|
|
// Check if session expired
|
|
if (new Date() > new Date(session.expiresAt)) {
|
|
await sessionStore.delete(sessionId);
|
|
return res.status(404).json({ message: "Session expired" });
|
|
}
|
|
|
|
// Return session status
|
|
res.json({
|
|
sessionId: session.sessionId,
|
|
userId: session.userId || "",
|
|
expires: session.expiresAt,
|
|
userSessionId: session.userSessionId || "",
|
|
Status: session.status || false
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Mobile App Authenticates Session
|
|
|
|
**This is what the MOBILE APP does** (not the web frontend):
|
|
|
|
### Mobile App Flow:
|
|
1. User scans QR code: `fastcheck://login?session=1AF3781BF6B94604B771AEA1D44FA63A`
|
|
2. Mobile app extracts sessionId: `1AF3781BF6B94604B771AEA1D44FA63A`
|
|
3. Mobile app authenticates user (PIN, biometrics, etc.)
|
|
4. Mobile app sends authentication request to backend:
|
|
|
|
```typescript
|
|
POST https://api.fastcheck.store/websession/authenticate
|
|
Headers: {
|
|
"Authorization": "Bearer {mobile_app_token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
Body: {
|
|
"sessionId": "1AF3781BF6B94604B771AEA1D44FA63A",
|
|
"userId": "kHaAe9roaC2uq63AKGE/8+Ti/t/iFro68QhEZ1dRGLo"
|
|
}
|
|
```
|
|
|
|
### Backend Implementation:
|
|
```javascript
|
|
app.post('/websession/authenticate', authenticateMobileApp, async (req, res) => {
|
|
const { sessionId, userId } = req.body;
|
|
const mobileUserId = req.user.id; // From mobile app authentication
|
|
|
|
// Verify the mobile user matches
|
|
if (userId !== mobileUserId) {
|
|
return res.status(403).json({ message: "Unauthorized" });
|
|
}
|
|
|
|
// Update session with user information
|
|
const userSessionId = generateUUID();
|
|
await sessionStore.update(sessionId, {
|
|
userId: userId,
|
|
userSessionId: userSessionId,
|
|
status: true,
|
|
authenticatedAt: new Date()
|
|
});
|
|
|
|
res.json({ message: "Session authenticated" });
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Logout (Delete Session)
|
|
|
|
### Frontend Request:
|
|
```typescript
|
|
DELETE https://api.fastcheck.store/websession/1AF3781BF6B94604B771AEA1D44FA63A
|
|
Headers: {
|
|
"Authorization": "{\"sessionID\": \"1AF3781BF6B94604B771AEA1D44FA63A\"}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
```
|
|
|
|
### Backend Implementation:
|
|
```javascript
|
|
app.delete('/websession/:sessionId', async (req, res) => {
|
|
const { sessionId } = req.params;
|
|
|
|
// Delete session from database/cache
|
|
await sessionStore.delete(sessionId);
|
|
|
|
res.json({ message: "Session deleted" });
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Authenticated API Requests
|
|
|
|
After login, all API requests include the sessionId in the Authorization header:
|
|
|
|
### Frontend Request:
|
|
```typescript
|
|
POST https://api.fastcheck.store/fastcheck
|
|
Headers: {
|
|
"Authorization": "{\"sessionID\": \"1AF3781BF6B94604B771AEA1D44FA63A\"}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
Body: {
|
|
"amount": 150000,
|
|
"currency": "RUB"
|
|
}
|
|
```
|
|
|
|
### Backend Authentication Middleware:
|
|
```javascript
|
|
// Middleware to verify session
|
|
const authenticateSession = async (req, res, next) => {
|
|
try {
|
|
// Parse Authorization header
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader) {
|
|
return res.status(401).json({ message: "not authorized" });
|
|
}
|
|
|
|
// Parse JSON from Authorization header
|
|
const { sessionID } = JSON.parse(authHeader);
|
|
|
|
// Verify session exists and is authenticated
|
|
const session = await sessionStore.get(sessionID);
|
|
|
|
if (!session || !session.status) {
|
|
return res.status(401).json({ message: "not authorized" });
|
|
}
|
|
|
|
// Check if session expired
|
|
if (new Date() > new Date(session.expiresAt)) {
|
|
await sessionStore.delete(sessionID);
|
|
return res.status(401).json({ message: "not authorized" });
|
|
}
|
|
|
|
// Attach user info to request
|
|
req.user = {
|
|
userId: session.userId,
|
|
userSessionId: session.userSessionId,
|
|
sessionId: sessionID
|
|
};
|
|
|
|
next();
|
|
} catch (error) {
|
|
return res.status(401).json({ message: "not authorized" });
|
|
}
|
|
};
|
|
|
|
// Use middleware on protected routes
|
|
app.post('/fastcheck', authenticateSession, async (req, res) => {
|
|
const { amount, currency } = req.body;
|
|
const userId = req.user.userId;
|
|
|
|
// Create FastCheck logic...
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## QR Code Data Format
|
|
|
|
### What the QR Code Contains:
|
|
```
|
|
fastcheck://login?session=1AF3781BF6B94604B771AEA1D44FA63A
|
|
```
|
|
|
|
**Format breakdown:**
|
|
- **Scheme**: `fastcheck://` - Deep link scheme for mobile app
|
|
- **Path**: `login` - Indicates this is a login QR code
|
|
- **Parameter**: `session={sessionId}` - The web session ID
|
|
|
|
### Frontend QR Code Implementation:
|
|
```typescript
|
|
// In login.component.ts
|
|
const sessionResponse = await createWebSession();
|
|
const qrData = `fastcheck://login?session=${sessionResponse.sessionId}`;
|
|
|
|
// QR code component displays this as a QR image
|
|
<qrcode [qrdata]="qrData" [width]="250"></qrcode>
|
|
```
|
|
|
|
---
|
|
|
|
## Database Schema Recommendations
|
|
|
|
### WebSession Table:
|
|
```sql
|
|
CREATE TABLE web_sessions (
|
|
session_id VARCHAR(64) PRIMARY KEY,
|
|
user_id VARCHAR(255),
|
|
user_session_id VARCHAR(64),
|
|
status BOOLEAN DEFAULT FALSE,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
authenticated_at TIMESTAMP,
|
|
INDEX idx_expires (expires_at),
|
|
INDEX idx_status (status)
|
|
);
|
|
|
|
-- Auto-delete expired sessions
|
|
CREATE EVENT cleanup_expired_sessions
|
|
ON SCHEDULE EVERY 1 HOUR
|
|
DO
|
|
DELETE FROM web_sessions WHERE expires_at < NOW();
|
|
```
|
|
|
|
### Or use Redis (Recommended for sessions):
|
|
```javascript
|
|
// Redis structure
|
|
const sessionKey = `websession:${sessionId}`;
|
|
await redis.setex(sessionKey, 300, JSON.stringify({
|
|
sessionId: sessionId,
|
|
userId: userId,
|
|
userSessionId: userSessionId,
|
|
status: true
|
|
}));
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
1. **Session Expiration**: Sessions should expire after 5 minutes if not authenticated
|
|
2. **HTTPS Only**: All communication must be over HTTPS
|
|
3. **CORS Configuration**: Configure CORS to allow frontend domain
|
|
4. **Session Cleanup**: Regularly clean up expired sessions
|
|
5. **Rate Limiting**: Limit polling requests to prevent abuse
|
|
6. **Mobile App Authentication**: Mobile app must authenticate before linking session
|
|
|
|
---
|
|
|
|
## Testing the Flow
|
|
|
|
### 1. Test Session Creation:
|
|
```bash
|
|
curl -X GET https://api.fastcheck.store/websession
|
|
```
|
|
|
|
Expected: New session with Status: false
|
|
|
|
### 2. Test Polling:
|
|
```bash
|
|
curl -X GET https://api.fastcheck.store/websession/{sessionId}
|
|
```
|
|
|
|
Expected: Same session, Status: false (until mobile app authenticates)
|
|
|
|
### 3. Test Mobile Authentication (simulate):
|
|
```bash
|
|
curl -X POST https://api.fastcheck.store/websession/authenticate \
|
|
-H "Authorization: Bearer {mobile_token}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"sessionId": "{sessionId}", "userId": "{userId}"}'
|
|
```
|
|
|
|
Expected: Session updated with Status: true
|
|
|
|
### 4. Test Polling After Auth:
|
|
```bash
|
|
curl -X GET https://api.fastcheck.store/websession/{sessionId}
|
|
```
|
|
|
|
Expected: Session with Status: true, userId populated
|
|
|
|
---
|
|
|
|
## Frontend Polling Implementation (Already Done)
|
|
|
|
```typescript
|
|
// In auth.service.ts
|
|
startPolling(sessionId: string): Observable<WebSession> {
|
|
return interval(2000).pipe( // Poll every 2 seconds
|
|
switchMap(() => this.checkWebSessionStatus(sessionId)),
|
|
tap(session => {
|
|
if (session.Status) {
|
|
this.setAuthenticated(session);
|
|
}
|
|
}),
|
|
takeWhile(session => !session.Status, true) // Stop when authenticated
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary for Backend Team
|
|
|
|
**Required Endpoints:**
|
|
1. ✅ `GET /websession` - Create session for QR
|
|
2. ✅ `GET /websession/:id` - Check session status (polled)
|
|
3. ⚠️ `POST /websession/authenticate` - Mobile app authenticates session (NEW)
|
|
4. ✅ `DELETE /websession/:id` - Logout
|
|
|
|
**Required Logic:**
|
|
- Generate unique session IDs
|
|
- Store sessions with expiration
|
|
- Mobile app updates session status
|
|
- Web frontend polls until Status = true
|
|
- All authenticated APIs verify session in Authorization header
|
|
|
|
**QR Code Data:**
|
|
- Format: `fastcheck://login?session={sessionId}`
|
|
- Mobile app parses and authenticates
|
|
- Web polls until mobile authenticates
|