Spring Boot can has a large number of settings that can be set with a file called application.yml
.
This file is already provided for you and is placed here for reference.
spring:
application:
name: IdmService
datasource:
url: jdbc:mysql://localhost:3306
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
server:
address: 0.0.0.0
port: 8081
error: # These settings are for debugging
include-exception: true
include-message: always
logging:
file:
name: ./IdmService.log
idm:
key-file-name: ec-key.json
access-token-expire: 30m
refresh-token-expire: 12h
max-refresh-token-life-time: 30d
💾 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 |
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 PRIMARY KEY |
role_id | INT |
NOT NULL PRIMARY KEY |
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 |
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.
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
, or - Putting the
@JsonInclude(JsonInclude.Include.NON_NULL)
on your Model class
All Result
objects are avaible as static constants inside of the com.github.klefstad_teaching.cs122b.core.result.IDMResults
class.
These can be used rather than creating your own.
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.
POST /register
📥 Request | ||
---|---|---|
Model | Example | |
email: String
password: char[] |
{
"email": "[email protected]",
"password": ["p", "a", "s", "s", "w", "o", "r", "d"]
} |
|
Key | Required | Description |
email | Yes | Must be of the form [email]@[domain].[extension], be between [6-32] characters (inclusive), and contain only alphanumeric characters |
password | Yes | Must 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 |
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.
An accessToken
is built as follows:
- Build a
JWTClaimsSet
with the following claims:subject
of the user's emailexpirationTime
of the currentTime + accessTokenExpireTime found in the configissueTime
the currentTimeclaim(JWTManager.CLAIM_ROLES)
of the user's roles (user a list of the provided Role enum)claim(JWTManager.CLAIM_ID)
of the user's id
- Build a
JWSHeader
with theJWTManager.JWS_ALGORITHM
and the following:keyID
of the ecKeyId found in your instance ofJWTManager
- found by calling
manager.getEcKey().getKeyID()
- found by calling
type
ofJWTManager.JWS_TYPE
- Build a
SignedJWT
with your createdJWTClaimsSet
andJWSHeader
- Sign by using calling
signedJWT.sign(jwtManager.getSigner())
- Serialize by calling
signedJWT.serialize()
A refreshToken
is created by calling UUID.randomUUID()
POST /login
📥 Request | ||
---|---|---|
Model | Example | |
email: String
password: char[] |
{
"email": "[email protected]",
"password": ["p", "a", "s", "s", "w", "o", "r", "d"]
} |
|
Key | Required | Description |
email | Yes | Must be of the form [email]@[domain].[extension], be between [6-32] characters (inclusive), and contain only alphanumeric characters |
password | Yes | Must 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 | Passwords do not match |
❗ 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 |
Creates a new accessToken
for the user given a valid and non-expired refreshToken
that belongs to the user.
If the refreshToken
passes basic validation ensure it follows this Flow Chart
POST /refresh
📥 Request | ||
---|---|---|
Model | Example | |
refreshToken: String |
{
"refreshToken": "c46fc3c2-9791-44d6-a86e-2922ad655284"
} |
|
Key | Required | Description |
refreshToken | Yes | Must 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 |
Authenticates a user's accessToken
to ensure it is both valid and non-expired.
An accessToken
can be verified in three steps:
- Verifying that the token is valid and issued by us by calling
jwt.verify(jwtManager.getVerifier())
- Checking that the claims are consistent with what we expect by calling
jwtManager.getJwtProcessor().process(jwt, null)
- Manually checking that the expireTime of the token has not passed.
POST /authenticate
📥 Request | ||
---|---|---|
Model | Example | |
accessToken: String |
{
"accessToken": "7f832c2e054ba732f7d4b7e26..."
} |
|
Key | Required | Description |
accessToken | Yes | Must 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 |