Skip to content

Repository files navigation

CryptWeb Backend

1. System Overview

CryptWeb is a Node.js/Express backend service providing:

  • Authentication (signup, login with JWT + cookie-based sessions)
  • Email Verification (OTP code verification flow)
  • Password Management (forgot + reset password with email token)
  • Session Management (multi-device sessions, logout, token refresh)
  • File Transfer Logging (persists completed P2P file transfer metadata)
  • File Transfer History (retrieve recent transfers for the authenticated user)
  • WebRTC Signaling (Socket.IO-based offer/answer/ICE exchange with active peer tracking)
  • Network Discovery (Socket.IO rooms grouped by client IP + REST endpoint for LAN IP and online users)
  • Health Check (application + database status)

Base URL: All REST routes are prefixed with /api. Source:src/app.tsapp.use('/api', v1Router)


2. Response Formats

All responses use one of two standardized classes.

ApiResponse (Success)

Source:src/utils/responses/ApiResponse.ts

{
"statusCode": 200,
"data": {},
"message": "Success message",
"success": true
}
FieldTypeDescription
statusCodenumberHTTP status code
dataTResponse payload (type varies per endpoint)
messagestringHuman-readable summary
successbooleantrue when statusCode < 400

ApiError (Error)

Source:src/utils/responses/ApiError.ts

{
"statusCode": 400,
"data": null,
"message": "Error description",
"success": false,
"errors": ["Optional array of validation details"]
}
FieldTypeDescription
statusCodenumberHTTP status code
datanullAlways null on errors
messagestringError description
successbooleanAlways false
errorsany[]Optional. Validation error details

3. Authentication Mechanism

Source:src/middlewares/auth.middleware.ts

Routes marked as ** Authenticated** require the following header:

Authorization: Bearer <accessToken>

Middleware behavior:

ScenarioStatusMessage
No Authorization header400Auth headers missing
Header present, no token401Access token required
Token expired / invalid JWT401Token expired
Unexpected error500Something went wrong at our end. Please Try again later

The middleware extracts sub from the JWT payload and attaches it as req.user.id.


4. Rate Limiting

Source:src/middlewares/rateLimitter.middleware.ts

LimiterWindowMax RequestsApplied To
authLimiter15 minutes5POST /api/v1/auth/login
signupLimiter10 hours20POST /api/v1/auth/signup
healthLimiter15 minutes5GET /api/v1/health
generalLimiter1 minute100Not found applied to any route in current code

When exceeded, the response body is a plain string message (e.g., "Too many login attempts, please try again later").


5. REST API Endpoints

5.1 Root

GET /api/

Auth: None
Rate Limit: None
Source:src/app.ts

Response (200):

{
"statusCode": 200,
"data": { "version": "<API_VERSION from env>" },
"message": "Welcome to auth service backend",
"success": true
}

5.2 Authentication

POST /api/v1/auth/signup

Auth: None
Rate Limit:signupLimiter (20 req / 10 hours)
Source:src/controllers/auth.controller.tssrc/services/auth.service.ts

Request Body:

{
"name": "string",
"email": "string",
"password": "string"
}

Validated using signupSchema (Zod). Fields: userName (mapped from name), email, password.

Response (201):

{
"statusCode": 201,
"data": {
"user": {
"id": "uuid",
"email": "string",
"name": "string",
"profile_picture": "string | undefined",
"created_on": "Date | undefined"
}
},
"message": "User created successfully",
"success": true
}

A verification code email is automatically sent after signup.

Errors:

StatusMessageCondition
400Missing input fieldsAny of name/email/password missing
400Invalid inputs fieldsZod validation failed (errors array contains details)
409Email already existsEmail already registered
500Something went wrong...Unexpected server error

POST /api/v1/auth/login

Auth: None
Rate Limit:authLimiter (5 req / 15 min)
Source:src/controllers/auth.controller.tssrc/services/auth.service.ts

Request Body:

{
"email": "string",
"password": "string"
}

Validated using loginSchema (Zod).

Response (200):

Sets three httpOnly, secure cookies: accessToken, refreshToken, deviceId.

{
"statusCode": 200,
"data": {
"user": {
"id": "uuid",
"email": "string",
"name": "string",
"profile_picture": "string | undefined",
"created_on": "Date | undefined"
},
"accessToken": "jwt string",
"refreshToken": "jwt string",
"deviceId": "hex string (20 chars)",
"sessionId": "uuid"
},
"message": "logged in successfully",
"success": true
}

Errors:

StatusMessageCondition
400Email and Password requiredMissing fields
400Invalid fieldsZod validation failed
404User not foundEmail not in database
400Invalid credentialsPassword mismatch
500There was unexpected error creating your session...Session creation failed
500Something went wrong...Unexpected server error

5.3 Email Verification

POST /api/v1/verify/email

Auth: None
Rate Limit: None
Source:src/controllers/verfiyUser.controller.tssrc/services/verify-email.service.ts

Request Body:

{
"email": "string",
"code": "string (6 characters)"
}

Response (200):

{
"statusCode": 200,
"data": null,
"message": "User verified successfully",
"success": true
}

Errors:

StatusMessageCondition
400Please enter 4 verification codeMissing code, email, or code length ≠ 6
400Invalid email addressEmail format validation failed
404User not foundEmail not in database
404No code found. Please signup or send click resend tokenNo verification token exists
200Email already verifiedtoken.used_at is set (not an error, returns ApiResponse)
400Token ExpiredCode older than 5 minutes (OTP_EXPIRY_MS: 300000)
400Invalid codebcrypt compare fails
500Something went wrong...Unexpected error

POST /api/v1/verify/resend-code

Auth: None
Rate Limit: None
Source:src/controllers/verfiyUser.controller.tssrc/services/verify-email.service.ts

Request Body:

{
"email": "string"
}

Response (201):

{
"statusCode": 201,
"data": null,
"message": "Code send to email",
"success": true
}

Errors:

StatusMessageCondition
400Email RequiredEmpty email
400Invalid email addressFormat check failed
404User not foundEmail not in database
200User already verifieduser.verified_at is set (returns ApiResponse, not error)
500Something went wrong...Unexpected error

5.4 Password Management

POST /api/v1/password/forgot

Auth: None
Rate Limit: None
Source:src/controllers/resetPassword.controller.tssrc/services/reset-password.service.ts

Request Body:

{
"email": "string"
}

Response (200):

{
"statusCode": 200,
"data": null,
"message": "If the email exists, a reset link has been sent.",
"success": true
}

A reset token is emailed asynchronously via process.nextTick.

Errors:

StatusMessageCondition
400Invalid email addressFormat validation
404User not foundEmail not in database
500Error generating reset password tokenToken storage failed
500Something went wrong...Unexpected error

POST /api/v1/password/reset

Auth: None
Rate Limit: None
Source:src/controllers/resetPassword.controller.tssrc/services/reset-password.service.ts

Request Body:

{
"email": "string",
"password": "string",
"confirmPassword": "string",
"token": "string (received via email)"
}

On success, all existing sessions for the user are invalidated (forced re-login).

Response (200):

{
"statusCode": 200,
"data": null,
"message": "Password reset successfull, Please Login again",
"success": true
}

Errors:

StatusMessageCondition
400Email and password requiredMissing fields
400Invalid email addressFormat validation
400Password does not matchpassword ≠ confirmPassword
400Invalid PasswordZod passwordSchema failed
200If the email exists, a reset link has been sent.Email not found (ambiguous response by design)
404No active reset token foundNo token in database
400Token already usedresetToken.used_at is set
400Reset Token ExpiredToken past expires_at
400Invalid TokenToken hash comparison fails
500Something went wrong...Unexpected error

5.5 Session Management

GET /api/v1/session/all

Auth: Authenticated
Rate Limit: None
Source:src/controllers/userSessions.controller.tssrc/services/user-session.service.ts

Query Parameters:

ParamTypeRequired
userIdstring (UUID)Yes

Response (200):

{
"statusCode": 200,
"data": [
{
"id": "uuid",
"user_id": "uuid",
"device_id": "string",
"device_type": {
"browser": "string",
"os": "string",
"device": "string",
"vendor": "string",
"model": "string"
},
"refresh_token": "string",
"expires_at": "ISO date",
"created_at": "ISO date"
}
],
"message": "sessions fetched successfully",
"success": true
}

Errors:

StatusMessageCondition
400User id requiredMissing userId
400Invalid user idNot a valid UUID
404No user session foundNo sessions exist
500Something went wrong...Unexpected error

DELETE /api/v1/session/log-out

Auth: Authenticated
Rate Limit: None
Source:src/controllers/userSessions.controller.tssrc/services/user-session.service.ts

Request Body:

{
"sessionId": "uuid",
"deviceId": "string"
}

On success, clears cookies: accessToken, refreshToken, deviceId.

Response (200):

{
"statusCode": 200,
"data": "deleted session uuid",
"message": "Session deleted successfully",
"success": true
}

Errors:

StatusMessageCondition
400Required fields missingMissing sessionId or deviceId
400Invalid user idsessionId not valid UUID
400No session foundSession doesn't exist
500Something went wrong...Unexpected error

POST /api/v1/session/log-out/all-sessions

Auth: Authenticated
Rate Limit: None
Source:src/controllers/userSessions.controller.tssrc/services/user-session.service.ts

Request Body: None. User ID is extracted from req.user.id (set by auth middleware).

Response (200):

{
"statusCode": 200,
"data": ["array of deleted session ids"],
"message": "Log out from all devices sucessfull",
"success": true
}

Errors:

StatusMessageCondition
400Invalid user idUUID validation failed
404No active user sessions foundNo sessions to delete
500Something went wrong...Unexpected error

POST /api/v1/session/renew

Auth: None
Rate Limit: None
Source:src/controllers/userSessions.controller.tssrc/services/tokens.service.ts

Request Body:

{
"refreshToken": "string",
"userId": "uuid",
"deviceId": "string",
"sessionId": "uuid"
}

Response (200):

{
"statusCode": 200,
"data": {
"accessToken": "new jwt string"
},
"message": "Access token generated successfully",
"success": true
}

Errors:

StatusMessageCondition
400Bad Request, Required fields are emptyMissing any field
400Invalid user idUUID validation failed
404User not founduserId not in database
404No session foundSession doesn't exist
400Refresh token expiredSession expires_at passed
400Invalid refresh Tokenbcrypt compare fails
500Something went wrong...Unexpected error

5.6 File Transfers

POST /api/v1/file-transfers/complete

Auth: Authenticated
Rate Limit: None
Source:src/controllers/fileTransfers.controller.tssrc/services/fileTransfers.service.tssrc/repositories/file_transfers.repo.ts

Request Body:

{
"senderEmail": "string",
"receiverEmail": "string",
"fileName": "string",
"fileSize": 1048576,
"fileType": "application/pdf",
"timeElapsed": 4500,
"transferType": "WebRTC"
}
FieldTypeDescription
senderEmailstringEmail of file sender
receiverEmailstringEmail of file receiver
fileNamestringName of the transferred file
fileSizenumberFile size in bytes (converted to MB on save)
fileTypestringMIME type
timeElapsednumberTransfer duration in milliseconds
transferTypestringTransfer method (e.g., "WebRTC", "Relay")

Response (201):

{
"statusCode": 201,
"data": {
"id": "uuid",
"sender": "uuid",
"receiver": "uuid",
"file_name": "string",
"file_size": 1048576,
"file_type": "string",
"time_elapsed": 4500,
"completed_at": "ISO date",
"transfer_type": "string"
},
"message": "File transfer recorded successfully",
"success": true
}

Errors:

StatusMessageCondition
400Invalid email addressEmail format validation failed
400Invalid file transfer dataMissing fileName/fileSize/fileType/timeElapsed/transferType
404Sender or receiver not foundEmail not found in database
500Something went wrong...Unexpected error

GET /api/v1/file-transfers/recent

Auth: Authenticated
Rate Limit: None
Source:src/controllers/fileTransfers.controller.tssrc/services/fileTransfers.service.tssrc/repositories/file_transfers.repo.ts

Query Parameters:

ParamTypeDefaultMaxDescription
limitnumber1050Number of transfers to return

Response (200):

{
"statusCode": 200,
"data": [
{
"id": "uuid",
"fileSize": 0.01,
"fileType": "text/markdown",
"timeElapsed": 0.045,
"transferType": "send",
"completedAt": "2026-07-18T12:28:09.034Z",
"senderName": "Alice",
"senderEmail": "alice@example.com",
"receiverName": "Bob",
"receiverEmail": "bob@example.com"
}
],
"message": "Recent transfers fetched",
"success": true
}

Errors:

StatusMessageCondition
401UnauthorizedNot authenticated
500Something went wrong...Unexpected error

5.7 Network

GET /api/network/ip

Auth: None
Rate Limit: None
Source:src/app.ts

Returns the server's LAN IP and all online users on the requesting client's network.

Response (200):

{
"statusCode": 200,
"data": {
"ip": "192.168.1.100",
"onlineUsers": [
{ "email": "alice@example.com", "name": "Alice" },
{ "email": "bob@example.com", "name": "Bob" }
]
},
"message": "Local network IP",
"success": true
}

5.8 Health Check

GET /api/v1/health

Auth: None
Rate Limit:healthLimiter (5 req / 15 min)
Source:src/controllers/health.controller.tssrc/services/health.service.ts

Response (200):

{
"statusCode": 200,
"data": {
"app": {
"status": "up",
"uptime": 123.45,
"memoryUsage": {
"rss": "45.23 MB",
"heapTotal": "30.12 MB",
"heapUsed": "25.67 MB",
"external": "1.23 MB"
}
},
"database": {
"status": "healthy",
"latency": "3 ms"
}
},
"message": "Health check successful",
"success": true
}

Response (503) — Database Down:

{
"statusCode": 503,
"data": {
"app": { "status": "up", "uptime": 123.45, "memoryUsage": { "..." } },
"database": { "status": "down", "latency": "0 ms" }
},
"message": "Service unavailable",
"success": false
}

6. Socket.IO Documentation

Source:src/components/signalling.ts

The Socket.IO server runs on the same HTTP server as Express. Connect using:

constsocket=io("http://localhost:<PORT>");

Registration is handled via the user:register event with database validation.
Client IP is detected via x-forwarded-for header (trust proxy) or socket.handshake.address, normalized for both IPv4 and IPv6, and used to group users into network rooms for local discovery.

Shared State: Socket state (emailToSocketMap, activePeers, ipToUsersMap) lives in src/utils/networkStore.ts and is also accessible from REST endpoints.


6.1 user:register

Direction: Client → Server

Payload:

{ "email": "string", "name": "string" }

Behavior:

  1. If name or email is missing → emits registration-error.
  2. Queries the database via Users.getByEmail(email).
  3. If user not found → emits registration-error.
  4. On success → adds to emailToSocketMap, joins socket to room network:<normalizedIP>, adds to ipToUsersMap, and broadcasts network:user-joined to all other sockets on the same IP.

Emitted responses:

registration-error:

{ "isOnline": null, "name": "<email>", "userExists": null, "message": "..." }

network:user-joined (broadcast to network room):

{
"email": "string",
"name": "string",
"onlineUsers": [{ "email": "string", "name": "string" }]
}

6.2 connection:request

Direction: Client → Server

Payload:

{ "from": "sender email", "to": "target email" }

Behavior:

  1. Validates target user exists in database.
  2. If target is online → emits status-update (isOnline: true) to sender and forwards connection:incoming to target.
  3. If target is offline → emits status-update (isOnline: false) to sender.

6.3 connection:response

Direction: Client → Server

Payload:

{ "from": "string", "to": "string", "accepted": true }

Behavior:

  • Forwards the response to the initiator. No peer tracking on this event.

6.4 users:connected

Direction: Client → Server

Payload:

{ "initiator": "string", "receiver": "string" }

Behavior:

  • Stores bidirectional mapping in activePeers map: initiator ↔ receiver.

6.5 network:users

Direction: Client → Server

Payload:(none)

Response:

network:users (Server → Client):

[
{ "email": "string", "name": "string" }
]

Returns all registered users connected from the same IP.


6.6 WebRTC Signaling (offer, answer, ice-candidate)

Direction: Client → Server (forwarded Server → Client)

Client sends:

{
"from": "sender email",
"to": "target email",
"offer"/"answer"/"candidate": "..."
}

If target is online → forwards payload to target.
If target is offline → emits user-status back to sender:

{ "isOnline": false, "message": "user offline" }

6.7 disconnect (automatic)

Behavior:

  1. Looks up the disconnected socket's email via getEmailBySocketId.
  2. If connected to an active peer → emits peer:disconnected to the peer's socket and cleans up activePeers.
  3. If registered on a network IP → removes from ipToUsersMap and broadcasts network:user-left to the remaining room members.
  4. Removes from emailToSocketMap.

Emitted to peer (peer:disconnected):

{
"name": "string",
"email": "string",
"message": "<name> went offline. Try again later"
}

Emitted to network room (network:user-left):

{
"email": "string",
"onlineUsers": [{ "email": "string", "name": "string" }]
}

6.8 Summary of Server → Client Events

Event NameWhen Emitted
registration-erroruser:register fails (user not in DB or internal error)
network:user-joinedA new user registered on the same network IP
network:user-leftA user on the same network IP disconnected
network:usersResponse to a network:users request
status-updateResponse to connection:request (online/offline status)
connection:incomingForwarded to target when someone requests a connection
connection:responseForwarded to initiator with the responder's decision
offerForwarded from another peer
answerForwarded from another peer
ice-candidateForwarded from another peer
user-statusTarget user offline (on offer/answer/ice-candidate)
peer:disconnectedAn active peer disconnected

7. In-Memory Data Structures

Source:src/utils/networkStore.ts

MapKey TypeValue TypePurpose
emailToSocketMapstring (email){ socketId: string, name: string }Maps registered emails to socket IDs
activePeersstring (email)string (peer email)Tracks active P2P connections
ipToUsersMapstring (IP)Set<string> (emails)Groups connected users by their client IP

8. Complete Flows

8.1 Signup → Verification → Login Flow

1. POST /api/v1/auth/signup → Creates user, sends OTP email
2. POST /api/v1/verify/email → Verifies OTP code
3. POST /api/v1/auth/login → Returns tokens + cookies + session

8.2 Password Reset Flow

1. POST /api/v1/password/forgot → Sends reset token via email
2. POST /api/v1/password/reset → Validates token, updates password, invalidates ALL sessions
3. POST /api/v1/auth/login → User must re-login

8.3 Token Refresh Flow

1. POST /api/v1/session/renew → Send refreshToken + userId + deviceId + sessionId
2. Receive new accessToken in response

8.4 WebRTC Signaling Flow

1. Both clients connect via Socket.IO
2. Both emit "user:register" with { email, name } → server validates against DB, joins network room
3. Initiator emits "connection:request" → server checks online status, forwards to target
4. Target responds with "connection:response" → server forwards back to initiator
5. Initiator emits "offer" → server forwards to receiver
6. Receiver emits "answer" → server forwards to initiator
7. Both exchange "ice-candidate" events
8. Once WebRTC connection established, either side emits "users:connected"
9. On disconnect, server emits "peer:disconnected" to the connected peer

8.5 File Transfer Logging Flow

1. File transfer happens over WebRTC DataChannel (not implemented in backend)
2. After completion, client calls POST /api/v1/file-transfers/complete
3. Server resolves emails to user UUIDs and persists the record
4. Client can retrieve transfer history via GET /api/v1/file-transfers/recent

9. Database Schema

Source:database/001_init.sql

users

ColumnTypeConstraints
idUUIDPK, default gen_random_uuid()
nameVARCHAR(30)NOT NULL
emailVARCHAR(255)NOT NULL, UNIQUE
password_hashTEXTNOT NULL
last_login_atTIMESTAMPTZ
profile_pictureTEXT
verified_atTIMESTAMPTZ
deleted_atTIMESTAMPTZ
created_onTIMESTAMPTZNOT NULL, DEFAULT NOW()
updated_onTIMESTAMPTZNOT NULL, DEFAULT NOW()

user_sessions

ColumnTypeConstraints
idUUIDPK
user_idUUIDFK → users(id), ON DELETE CASCADE
device_idTEXTUNIQUE, NOT NULL
refresh_tokenTEXTNOT NULL
expires_atTIMESTAMPTZNOT NULL
created_atTIMESTAMPTZDEFAULT NOW()
device_typeJSONBDEFAULT {}

email_verification_tokens

ColumnTypeConstraints
idUUIDPK
user_idUUIDNOT NULL, UNIQUE, FK → users(id)
token_hashTEXTNOT NULL
used_atTIMESTAMPTZDEFAULT NULL
revoked_atTIMESTAMPTZDEFAULT NULL
created_atTIMESTAMPTZDEFAULT NOW()
expires_atTIMESTAMPTZNOT NULL

password_reset_tokens

ColumnTypeConstraints
idUUIDPK
user_idUUIDNOT NULL, FK → users(id)
expires_atTIMESTAMPTZNOT NULL
created_atTIMESTAMPTZDEFAULT NOW()
token_hashTEXTNOT NULL
used_atTIMESTAMPTZDEFAULT NULL

file_transfers

ColumnTypeConstraints
idUUIDPK
senderUUIDNOT NULL, FK → users(id)
receiverUUIDNOT NULL, FK → users(id)
file_sizeNUMERIC(10,2)NOT NULL (in MB)
file_typeTEXTNOT NULL
time_elapsedDOUBLE PRECISIONNOT NULL
completed_atTIMESTAMPTZDEFAULT NULL
transfer_typeTEXTNOT NULL

10. Edge Cases Handled In Code

ScenarioWhere HandledResponse
Missing required fieldsAll services400 with specific message
Invalid email formatVerify, Reset, FileTransfer services400 Invalid email address
Invalid UUID formatSession service, Token service400 Invalid user id
User not found by emailAuth, Verify, Reset, Signaling404 User not found
User not found by IDToken service404 User not found
Duplicate email on signupAuth service409 Email already exists
Password mismatch on loginAuth service400 Invalid credentials
Password ≠ confirmPassword on resetReset service400 Password does not match
Expired OTP codeVerify service400 Token Expired
Already verified emailVerify service200 Email already verified
Reset token already usedReset service400 Token already used
Reset token expiredReset service400 Reset Token Expired
Refresh token expiredToken service400 Refresh token expired
Invalid refresh token hashToken service400 Invalid refresh Token
No active sessions foundSession service404 No user session found
Target user offline (socket)Signaling (offer/answer/ice)user-status event emitted
Unregistered email on socket registerSignalingregistration-error event
Active peer disconnectsSignaling (disconnect handler)peer:disconnected event to peer
Network user joinsSignaling (user:register)network:user-joined broadcast
Network user leavesSignaling (disconnect handler)network:user-left broadcast
IPv4-mapped IPv6 (::ffff:...)normalizeIP in networkStore.tsStripped to IPv4
Rate limit exceededAuth login, signup, health429 with text message

11. Cookie Configuration

Source:src/constants.ts

{
"httpOnly": true,
"secure": true
}

Cookies set on login: accessToken, refreshToken, deviceId.
Cookies cleared on logout: accessToken, refreshToken, deviceId.


About

Peer-to-peer file transfer system enabling fast, secure, and server-independent data exchange.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages