diff --git a/backend/pms/README.md b/backend/pms/README.md index 4fc0db9..be41bbc 100644 --- a/backend/pms/README.md +++ b/backend/pms/README.md @@ -8,7 +8,7 @@ docker-compose up -d --build ``` ---- +
#### 2. Create DB @@ -16,7 +16,7 @@ docker-compose up -d --build CREATE DATABASE "pms-db"; ``` ---- +
#### 3. Member Details @@ -35,13 +35,13 @@ CREATE DATABASE "pms-db"; > password: 123 ``` ---- +
#### 4. Open API Documentation ```http://localhost:8080/swagger-ui/index.html``` ---- +
#### 5. Create Access Token @@ -64,9 +64,12 @@ response: "refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6W3sicm9sZSI6IkFkbWluIn1dLCJleHAiOjE3MDcwNzY3NjEsImlhdCI6MTcwNzA3NTU2MSwiZW1haWwiOiJhQGEuY29tIn0.HmEI79h6_IZBsZDv73kMd6XTcfz5PJBq2WrZPXNXBt1vco-osuq5PiEzDPIAn_KYTVvlb8CSlEybyJMqss8tKQ" } ``` +
--- +
+ ## II. File Service
@@ -88,7 +91,7 @@ Response } ``` ---- +
#### 2. Display or Download API @@ -97,8 +100,12 @@ Request: curl --location 'http://localhost:8080/api/v1/files/16a1ddc7-b440-4dc7-8b4b-aba60a567d4a/download' ``` +
+ --- +
+ ## III. Email Service @@ -147,3 +154,56 @@ curl --location 'http://localhost:8080/api/v1/emails/send' \ "recipient": "a@a.com" }' ``` + +
+ +---- + +
+ +## IV. System Design + +### 1. Token Issuance + +
+ +![Token Issuance](out/design/1_issue_token/1_issue_token.png) + +
+ +### 2. Token Verification +![Token Verification](out/design/2_verify_token/2_verify_token.png) + +
+ +### 3. Token Renewal + +![Token Renewal](out/design/3_renew_token/3_renew_token.png) + +
+ +### 4. File Service + +![File Service](out/design/4_file_service/4_file_service.png) + + +
+ +### 5. SMTP Service +![alt text](out/design/5_smtp/5_smtp.png) + +
+ +Testing + +```plantuml.server +@startuml + test -> hello: hi +@enduml +``` + +```plantuml +@startuml + test -> hello: hi +@enduml +``` \ No newline at end of file diff --git a/backend/pms/design/1_issue_token.plantuml b/backend/pms/design/1_issue_token.plantuml new file mode 100644 index 0000000..28b4de0 --- /dev/null +++ b/backend/pms/design/1_issue_token.plantuml @@ -0,0 +1,44 @@ +@startuml + + participant Frontend as front + participant Backend as back + participant "JWT Filter" as filter + participant AuthSerivce as auth + + ||| + autonumber "0." + front -> back: request to create a token + note left + POST: **//.../auth/token//** + { + "email": "a@a.com" + "password": "***" + } + end note + back -> filter: request token + filter -> filter: skip JWT token verification + filter -> auth: request token + auth -> auth: authenticate email & password \n if fail send message "Invalid Credential" + auth -> auth: + note right + create and sign a JWT access and refresh token + with secret key. + claims: + - email + - role + - issued date + - expired date + end note + + auth --> front: issue a token response: + note right of front + { + "accessToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij" + "refreshToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij" + } + end note + +||| + + +@enduml \ No newline at end of file diff --git a/backend/pms/design/2_verify_token.plantuml b/backend/pms/design/2_verify_token.plantuml new file mode 100644 index 0000000..2d40f4b --- /dev/null +++ b/backend/pms/design/2_verify_token.plantuml @@ -0,0 +1,56 @@ +@startuml + + participant Frontend as front + participant Backend as back + participant "JWT Filter" as filter + participant AuthSerivce as auth + participant ProfileController as controller + + skinparam participant { + padding 50 + } + + ||| + autonumber "0." + front -> back: request to get authenticated resource + note right front + GET: **//.../profile//** + Header: + Authorization: Bearer ASDFkoeq... + end note + ||| + back -> filter: request resource + ||| + filter -> filter: verify Bearer JWT token + ||| + filter -> auth: request resource + auth -> auth: verify JWT token with secret \n if fail send message "Invalid Token" + ||| + auth -> auth: validate JWT token if expired \n if fail send message "Expired Token" + ||| + auth -> auth: + note right + extract info from JWT Token + claims: + - email + - role + end note + auth -> filter: return JWT Info + ||| + filter -> filter: authenticated email and role into SecurityContextHolder + filter -> controller: forward requst to access resoure + controller -> controller: get resource + + controller --> front: response profile: + note right of front + { + "name":"Mr.a" + "email": "a@a.com" + ... + } + end note + +||| + + +@enduml \ No newline at end of file diff --git a/backend/pms/design/3_renew_token.plantuml b/backend/pms/design/3_renew_token.plantuml new file mode 100644 index 0000000..03bab23 --- /dev/null +++ b/backend/pms/design/3_renew_token.plantuml @@ -0,0 +1,60 @@ +@startuml + + participant Frontend as front + participant Backend as back + participant "JWT Filter" as filter + participant AuthSerivce as auth + skinparam participant { + padding 20 + } + +||| +autonumber "0." +note over front + POST: **//.../auth/token/refresh//** + Header: + Authorization: Bearer ASDFkoeq... +end note +front -> back: request to renew token +back -> filter: request to filter +||| +filter -> filter: verify Bearer JWT token +||| +filter -> auth: request to auth +auth -> auth: verify JWT refresh token with secret \n if fail send message "Invalid Token" +||| +auth -> auth: validate JWT refresh token if expired \n if fail send message "Expired Token" +||| +auth -> auth: +note right + extract info from JWT refresh token + claims: + - email + - role +end note +||| +auth -> auth: validate and authenticate user +||| +auth -> auth: +note right + create and sign a JWT access and refresh token + with secret key. + claims: + - email + - role + - issued date + - expired date +end note + +auth --> front: issue a token response: +note right of front + { + "accessToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij" + "refreshToken":"ASDFkoeqncE/wej.sdfweWE/dfwe.WEFwfoweij" + } +end note + +||| + + +@enduml \ No newline at end of file diff --git a/backend/pms/design/4_file_service.plantuml b/backend/pms/design/4_file_service.plantuml new file mode 100644 index 0000000..7a16df2 --- /dev/null +++ b/backend/pms/design/4_file_service.plantuml @@ -0,0 +1,106 @@ +@startuml + + participant Frontend as front + participant Backend as back + participant "JWT Filter" as filter + participant AuthSerivce as auth + participant FileController as controller + participant FileService as service + + skinparam participant { + padding 50 + } + skinparam BoxPadding 50 + + ||| + +group Upload File + autonumber "0." + front -> back: request to upload file + note over front + POST: **//.../files/upload//** + Content-Type: + multipart/form-data; + boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW + Header: + Authorization: Bearer ASDFkoeq... + Form: + ------WebKitFormBoundary7MA4YWxkTrZu0gW + Content-Disposition: + form-data; name="file"; filename=".../avatar.jpg" + Content-Type: image/jpeg + + end note + ||| + back -> filter: upload file + ||| + filter -> filter: verify Bearer JWT token + ||| + filter -> auth: upload file + auth -> auth: verify JWT token with secret \n if fail send message "Invalid Token" + ||| + auth -> auth: validate JWT token if expired \n if fail send message "Expired Token" + ||| + auth -> auth: + note right + extract info from JWT Token + claims: + - email + - role + end note + auth -> filter: return JWT Info + ||| + filter -> filter: authenticated email and role into SecurityContextHolder + filter -> controller: upload file + controller -> service: send file to servcie + service -> service: + note right + - generate fileKey + - extract file metadata (filename, size) + - write the file to file storage mapped by fileKey + - generate a url to download file + - save (filekey, file metatdata, url) to table + end note + ||| + service -> controller: response FileInfo + + + + controller --> front: response file info: + note right of front + { + "url": "http://localhost:8080/api/v1/files/51e2d007-1799-4f97-b3fd-2090b4cad885/download", + "key": "51e2d007-1799-4f97-b3fd-2090b4cad885" + } + end note + +||| +end +||| + +group Download File +||| + + autonumber "0." + front -> back: download file + note over front + GET: **//.../files/{filekey}/download//** + + end note + ||| + back -> filter: download file + ||| + filter -> filter: skip JWT token verification + ||| + filter -> controller: download file + controller -> service: send file to servcie + service -> service: get file metadata by **{filekey}** from DB and \nvalidate if not exist throw message "file not found" + service -> service: read file by filekey + service -> controller: return file + controller -> front: stream file to client + +||| +end + + +@enduml \ No newline at end of file diff --git a/backend/pms/design/5_smtp.plantuml b/backend/pms/design/5_smtp.plantuml new file mode 100644 index 0000000..602e5b2 --- /dev/null +++ b/backend/pms/design/5_smtp.plantuml @@ -0,0 +1,57 @@ +@startuml + + participant Frontend as front + participant Backend as back + participant "JWT Filter" as filter + participant AuthSerivce as auth + participant EmailController as controller + participant EmailService as service + + skinparam participant { + padding 50 + } + + ||| + autonumber "0." + front -> back: request to send an email + note right front + GET: **//.../files/send//** + Header: + Authorization: Bearer ASDFkoeq... + { + "email": "a@a.com", // recipient + "content": "everyone good", + "recipient": "dengbunthai@gmail.com" + } + end note + ||| + back -> filter: request to send an email + ||| + filter -> filter: verify Bearer JWT token + ||| + filter -> auth: request to send an email + auth -> auth: verify JWT token with secret \n if fail send message "Invalid Token" + ||| + auth -> auth: validate JWT token if expired \n if fail send message "Expired Token" + ||| + auth -> auth: + note right + extract info from JWT Token + claims: + - email + - role + end note + auth -> filter: return JWT Info + ||| + filter -> filter: authenticated email and role into SecurityContextHolder + filter -> controller: forward requst to send the email + controller -> service: request service to send email + service -> Recipient: send email to recipient + + + controller --> front: response 204 no content + +||| + + +@enduml \ No newline at end of file diff --git a/backend/pms/out/design/1_issue_token/1_issue_token.png b/backend/pms/out/design/1_issue_token/1_issue_token.png new file mode 100644 index 0000000..71f23df Binary files /dev/null and b/backend/pms/out/design/1_issue_token/1_issue_token.png differ diff --git a/backend/pms/out/design/2_verify_token/2_verify_token.png b/backend/pms/out/design/2_verify_token/2_verify_token.png new file mode 100644 index 0000000..d5b7f4b Binary files /dev/null and b/backend/pms/out/design/2_verify_token/2_verify_token.png differ diff --git a/backend/pms/out/design/3_renew_token/3_renew_token.png b/backend/pms/out/design/3_renew_token/3_renew_token.png new file mode 100644 index 0000000..944e6d2 Binary files /dev/null and b/backend/pms/out/design/3_renew_token/3_renew_token.png differ diff --git a/backend/pms/out/design/4_file_service/4_file_service.png b/backend/pms/out/design/4_file_service/4_file_service.png new file mode 100644 index 0000000..f7c350a Binary files /dev/null and b/backend/pms/out/design/4_file_service/4_file_service.png differ diff --git a/backend/pms/out/design/5_smtp/5_smtp.png b/backend/pms/out/design/5_smtp/5_smtp.png new file mode 100644 index 0000000..09409a1 Binary files /dev/null and b/backend/pms/out/design/5_smtp/5_smtp.png differ