Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

40 Commits

Folders and files

Repository files navigation

Stack Backend 1 - The IDM Service

  1. POST: Register
  2. POST: Login
  3. POST: Refresh
  4. POST: Authenticate

Application

Our application depends on a lot of files and resources to be able to run correctly. These files have been provided for you and are listed here for your reference. These files should NEVER be modified and must be left AS IS.

pom.xml

Maven gets all its settings from a file called pom.xml. This file determines the dependencies we will use in our project as well as the plugins we use for compiling, testing, building, ect..

application.yml

Spring Boot has a large number of settings that can be set with a file called application.yml. We have already created this file for you and have filled it with some settings. There is a file for the main application as well as one for the tests.

Resources

There are two folders in this project that contain resources, and application settings, as well as files required for the tests.

Tests

There is a Single class that contain all of our test cases:

Database

Schemas

🗄 idm

Tables

💾 idm.token_status
Column Name Type Attributes
id INT NOT NULL PRIMARY KEY
value VARCHAR(32) NOT NULL
💾 idm.user_status
Column Name Type Attributes
id INT NOT NULL PRIMARY KEY
value VARCHAR(32) NOT NULL
💾 idm.role
Column Name Type Attributes
id INT NOT NULL PRIMARY KEY
name VARCHAR(32) NOT NULL
description VARCHAR(128) NOT NULL
precedence INT NOT NULL
💾 idm.user
Column Name Type Attributes
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
email VARCHAR(32) NOT NULL UNIQUE
user_status_id INT NOT NULL
salt CHAR(8) NOT NULL
hashed_password CHAR(88) NOT NULL
Constraints
FOREIGN KEY (user_status_id) REFERENCES idm.user_status (id) ON UPDATE CASCADE ON DELETE RESTRICT
💾 idm.refresh_token
Column Name Type Attributes
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
token CHAR(36) NOT NULL UNIQUE
user_id INT NOT NULL
token_status_id INT NOT NULL
expire_time TIMESTAMP NOT NULL
max_life_time TIMESTAMP NOT NULL
Constraints
FOREIGN KEY (user_id) REFERENCES idm.user (id) ON UPDATE CASCADE ON DELETE CASCADE
FOREIGN KEY (token_status_id) REFERENCES idm.token_status (id) ON UPDATE CASCADE ON DELETE RESTRICT
💾 idm.user_role
Column Name Type Attributes
user_id INT NOT NULL
role_id INT NOT NULL
Constraints
PRIMARY KEY (user_id, role_id)
FOREIGN KEY (user_id) REFERENCES idm.user (id) ON UPDATE CASCADE ON DELETE CASCADE
FOREIGN KEY (role_id) REFERENCES idm.role (id) ON UPDATE CASCADE ON DELETE RESTRICT

Initial Data

All the data to initialize your database is found in the db folder here: db folder

Notes

Order of Validation

All ❗ 400: Bad Request Results must be checked first, and returned before any other action is made.
The order of the checks within ❗ 400: Bad Request is not tested as each Result is tested individually.

JsonInclude

In the case of non-successful results, where values are expected, the values should not be included, for example.

{
   "result": {
      "code": 32,
      "message": "Data contains invalid integers"
   },
   "value": null 
}

the value key should not be included:

{
   "result": {
      "code": 32,
      "message": "Data contains invalid integers"
   }
}

This is done by insuring that all null values are dropped by either:

  • Having your Model extend ResponseModel<Model>, or
  • Putting the @JsonInclude(JsonInclude.Include.NON_NULL) on your Model class

Result

All Result objects are available as static constants inside of the com.gitcodings.stack.core.result.IDMResults class. These can be used rather than creating your own.

DB Constants

We have provided some Enum classes in The Type Module to help manage constants in the DB, such as idm.role, idm.user_status, and idm.token_status. These are to help translate between id in the table and type the id corresponds with and make the code more readable.

Setting Expire Times

In our IDMServiceConfig we are given Duration for each of the expire types. When we are setting our expire time's in our AccessToken and RefreshToken we create a Instant that represents the time and date at which they expire.

We do this by calling Instant.now().plus(duration). More information on Instant and Duration here: Activty 2

Endpoints

Register

Allows users to create login details given a valid email and password. Password must be hashed and salted with both values being stored in the idm.user table. The user is given no roles, as well as being assigned the user_status of ACTIVE, by default.

Path

POST /register

API

📥  Request
Model Example
email: String
password: char[]
{
    "email": "username@example.com",
    "password": ["p", "a", "s", "s", "w", "o", "r", "d"]
}
Key Required Description
emailYesMust be of the form [email]@[domain].[extension], be between [6-32] characters (inclusive), and contain only alphanumeric characters
passwordYesMust be between [10-20] alphanumeric characters (inclusive), contain at least one uppercase alpha, one lowercase alpha, and one numeric
📤  Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 1010,
        "message": "User registered successfully"
    }
}
📦  Results
Status Code Message
✅ 200: Ok 1010 User registered successfully
❗ 409: Conflict 1011 User with this email already exists
❗ 400: Bad Request 1000 Password does not meet length requirements
❗ 400: Bad Request 1001 Password does not meet character requirement
❗ 400: Bad Request 1002 Email address has invalid format
❗ 400: Bad Request 1003 Email address has invalid length

Login

Allows users to login with credentials created in the /register endpoint. Returns both a accessToken and a refreshToken (with the refreshToken being stored in the idm.refresh_token table). Both tokens expiration dates are determined by the values supplied in the application.yml file.

AccessToken

An accessToken is built as follows:

  1. Build a JWTClaimsSet with the following claims:
    1. subject of the user's email
    2. expirationTime of the "Current Time" (use Instant for this) + accessTokenExpireTime found in the config
    3. issueTime the currentTime
    4. claim(JWTManager.CLAIM_ROLES) of the user's roles (user a list of the provided Role enum)
      • If the user has no roles then just place a empty list here. The JWT Requires that there is a value for JWTManager.CLAIM_ROLES
    5. claim(JWTManager.CLAIM_ID) of the user's id
  2. Build a JWSHeader with the JWTManager.JWS_ALGORITHM and the following:
    1. keyID of the ecKeyId found in your instance of JWTManager
      • found by calling manager.getEcKey().getKeyID()
    2. type of JWTManager.JWS_TYPE
  3. Build a SignedJWT with your created JWTClaimsSet and JWSHeader
  4. Sign by using calling signedJWT.sign(jwtManager.getSigner())
  5. Serialize by calling signedJWT.serialize()

RefreshToken

A refreshToken is created by creating an instance of the RefreshToken class that is provided for you and having the token attribute be set to a random UUID (in string format). You can get a random UUID by using: UUID.randomUUID() and you can get the string formated UUID by calling the toString() function on the UUID. The token will have a token_status of ACTIVE, by default. Remember this refreshToken must be stored in the db to be able to refrence later

The token attribute of RefreshToken is what you return to the user in the response labeled as refreshToken.

Path

POST /login

API

📥  Request
Model Example
email: String
password: char[]
{
    "email": "username@example.com",
    "password": ["p", "a", "s", "s", "w", "o", "r", "d"]
}
Key Required Description
emailYesMust be of the form [email]@[domain].[extension], be between [6-32] characters (inclusive), and contain only alphanumeric characters
passwordYesMust be between [10-20] alphanumeric characters (inclusive), contain at least one uppercase alpha, one lowercase alpha, and one numeric
📤  Response
Model Example
result: Result
    code: Integer
    message: String
accessToken: String (nullable)
refreshToken: String (nullable)
{
    "result": {
        "code": 1020,
        "message": "User logged in successfully"
    },
    "accessToken": "7f832c2e054ba732f7d4b7e26..."
    "refreshToken": "c46fc3c2-9791-44d6-a86e-2922ad655284"
}
📦  Results
Status Code Message
✅ 200: Ok 1020 User logged in successfully
❗ 401: Unauthorized 1021 User not found
❗ 403: Forbidden 1022 Invalid credentials
❗ 403: Forbidden 1023 User is locked
❗ 403: Forbidden 1024 User is banned
❗ 400: Bad Request 1000 Password does not meet length requirements
❗ 400: Bad Request 1001 Password does not meet character requirement
❗ 400: Bad Request 1002 Email address has invalid format
❗ 400: Bad Request 1003 Email address has invalid length

Refresh

Creates a new accessToken for the user given a valid and non-expired refreshToken that belongs to the user.

To validate if a UUID has the correct format we can use UUID.fromString(refreshToken). If the UUID has an invalid format this will throw an exception that you can catch to throw the correct Result to the user.

When we are given a refreshToken (this corrosponds to the token attribute in idm.refreshToken) in this endpoint we need to retrieve all the addition traits of the token (user_id, token_status_id, expire_time, max_life_time) from the database.

Anytime we update the RefreshToken's attributes we must update it with the DB, and anytime we create a new RefreshToken we must also insert it into the DB.

Flowchart

If the refreshToken passes basic validation ensure it follows this Flow Chart

Path

POST /refresh

API

📥  Request
Model Example
refreshToken: String
{
    "refreshToken": "c46fc3c2-9791-44d6-a86e-2922ad655284"
}
Key Required Description
refreshTokenYesMust be 36 characters in length and be a valid UUID formatted string
📤  Response
Model Example
result: Result
    code: Integer
    message: String
accessToken: String (nullable)
refreshToken: String (nullable)
{
    "result": {
        "code": 1030,
        "message": "AccessToken has been refreshed"
    },
    "accessToken": "7f832c2e054ba732f7d4b7e26..."
    "refreshToken": "c46fc3c2-9791-44d6-a86e-2922ad655284"
}
📦  Results
Status Code Message
✅ 200: Ok 1030 AccessToken has been refreshed
❗ 401: Unauthorized 1031 RefreshToken is expired
❗ 401: Unauthorized 1032 RefreshToken is revoked
❗ 401: Unauthorized 1033 RefreshToken not found
❗ 400: Bad Request 1032 RefreshToken has invalid length
❗ 400: Bad Request 1033 RefreshToken has invalid format

Authenticate

Authenticates a user's accessToken to ensure it is both valid and non-expired.

We are given the JWT in serialized format, so we will need to use SignedJWT.parse() in order to turn it back into SignedJWT

Verification

An accessToken can be verified in three steps:

  1. Verifying that the token is valid and issued by us by calling jwt.verify(jwtManager.getVerifier())
  2. Checking that the claims are consistent with what we expect by calling jwtManager.getJwtProcessor().process(jwt, null)
  3. Manually checking that the expireTime of the token has not passed.

Path

POST /authenticate

API

📥  Request
Model Example
accessToken: String
{
    "accessToken": "7f832c2e054ba732f7d4b7e26..."
}
Key Required Description
accessTokenYesMust be a valid JWT encoded string
📤  Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 1040,
        "message": "AccessToken is valid"
    }
}
📦  Results
Status Code Message
✅ 200: Ok 1040 AccessToken is valid
❗ 401: Unauthorized 1041 AccessToken is expired
❗ 401: Unauthorized 1042 AccessToken is invalid

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages