You only need to implement one user login REST API. Below are some specific requirements.
- A user can login with a username and password
- Return success and a JWT token if username and password are correct
- Return fail if username and password are not matched
- A user has a maximum of 3 attempts within 5 minutes, otherwise, the user will be locked.
- Return fail if a user is locked
- Use Nest.js framework and typescript is required.
- Use MongoDB (You can design your own data structure, just create your own seed data. No need to do user signup).
- Unit testing is required
- Integration testing is required
- Dockerize your code and your database
- Upload your project to github and write a proper readme.
- In Linux or MacOS: Please use makefile as the entry, refer to Configs.
- In Windows:
Please delete the docker-compose.yaml and rename the docker-compose-windows.yaml to docker-compose.yaml, then run command:
docker-compose up --detach --remove-orphans --build
Tech Stack: Nest.js + docker + mongodb + redis
uuid: "Universally Unique IDentifier"
username: "username"
password: "a md5 encoded password string"
creation_date: "timestamp of account creation time"
last_login: "timestamp of last login time"
status: "Enumerate indicating account status, 0-Locked, 1-Active"
When Mongodb container created, the script mongo-init.sh will be invoked automatically to initialize the db collection and insert dummy data docker-entrypoint-initdb.d/mongo-init.sh
set -e
echo
mongosh -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD <<EOF
use $MONGO_INITDB_DATABASE
db.createUser(
{
user: '$MONGO_INITDB_ROOT_USERNAME',
pwd: '$MONGO_INITDB_ROOT_PASSWORD',
roles: [
{
role: "readWrite",
db: '$MONGO_INITDB_DATABASE'
}
]
}
);
db.createCollection('users', { capped: false });
db.users.insert([
{
"uuid": "uuid_001",
"username": "demo_user",
"password": "813ec4fed5876061b8fa468b7f309aa8",
"creation_date": "2023-11-30",
"last_login": "2023-11-30",
"status": 1
}
]);
EOF
Redis is used for storing the failed login attempt in 5 minutes using the TTL feature which is only available for Redis version >= 6.0.0)
key-value pair as below, and will expire after TTL. Updating the value should not reset the TTL.
username: number of failed attempts [0-3)
application should be built in the environment with node version >= 18.0
Please initiate/stop/clean the containers with the Makefile.
make refresh - this command will initiate the containers
make log - this command will continuously print the log from all containers in real-time
make start - this command will start all of the stopped containers
make stop - this command will stop all running containers
make down - this command will stop and clean all containers
redis and Mongodb are persistent in Docker local volume
version: '3'
services:
mongo_local:
container_name: mongo_local
image: mongo:7.0
ports:
- '${DB_PORT}:${DB_PORT}'
volumes:
- ./docker-entrypoint-initdb.d/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro
- mongo_local:/data
environment:
- MONGO_INITDB_ROOT_USERNAME=${DB_USERNAME}
- MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD}
- MONGO_INITDB_DATABASE=${MONGO_INITDB_DATABASE}
networks:
- sapia-network
# could be dynamodb
redis_local:
container_name: redis_local
image: redis:7.0.0
restart: always
volumes:
- redis_local:/data
ports:
- '${REDIS_PORT}:${REDIS_PORT}'
networks:
- sapia-network
sapia_authorizer:
container_name: sapia_authorizer
build:
context: ./authorizer
image: sapia_authorizer
ports:
- '3000:3000'
environment:
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DB_NAME=${MONGO_INITDB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- TOKEN_EXPIRE=${TOKEN_EXPIRE}
- TOKEN_SECRET=${TOKEN_SECRET}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
networks:
- sapia-network
networks:
sapia-network:
driver: bridge
volumes:
redis_local:
driver: local
mongo_local:
driver: local
When successful login, an access token will be responded (the token is validated for 1 hour by default
When the wrong credential is provided, including the inexistent username or mismatched credentials, the request fails with HTTP status code 401, and a message "username or password not found"
When more than 3 times unsuccessful tries on the same account, the account will be locked. The request fails with HTTP status code 403, and a message "Your account is locked, please contact admin to verify your identity."
- The solution doesn't apply any lock while counting login attempts, which could have a concurrency issue (dirty read/write) when a large amount of login attempts happen simultaneously. But is acceptable in this scenario, if concurrency issue is the case, then a pessimistic lock need to be applied when updating login attempts data. This could be implemented by acquiring a key, which could be a record in redis, piror to each failed login attempt.
- Refresh token could be generated for user to exchange for another access token when its expired.