multiple changes
This commit is contained in:
		
							parent
							
								
									dc5c92c1e8
								
							
						
					
					
						commit
						86ab1a451b
					
				
							
								
								
									
										10
									
								
								.yaak/yaak.fl_1twfKS3Z0A.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.yaak/yaak.fl_1twfKS3Z0A.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					type: folder
 | 
				
			||||||
 | 
					model: folder
 | 
				
			||||||
 | 
					id: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					createdAt: 2025-01-04T16:33:57
 | 
				
			||||||
 | 
					updatedAt: 2025-01-12T08:36:42
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: null
 | 
				
			||||||
 | 
					name: API Test
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					sortPriority: 1000.0
 | 
				
			||||||
							
								
								
									
										40
									
								
								.yaak/yaak.rq_1YHYKIkG8x.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.yaak/yaak.rq_1YHYKIkG8x.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_1YHYKIkG8x
 | 
				
			||||||
 | 
					createdAt: 2025-01-06T11:35:07
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:29:24.409065
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "signature": "untrusted comment: {\"timestamp\":\"2025-01-07T21:23:48.632759\",\"user_id\":\"TestUser\"}\nRUSvcn8DqG4zxAhjcXsihWr/GlBX5bqPs64sPuU2PsFWgALzwkv+dRUF2on5Xr2RFMAl+U0WRwz1jR65FbEHGnzHP6/Lq48IcgM=\ntrusted comment: timestamp:1736284801\nLHdY9CWqzzhFtkGGUXuaYRiFrpg6CId8rX2zRJJigmWJxCPx7+d3ZtWPel4TmHUB21Ce6C84ObFCZRwWE8v6Bw==\n",
 | 
				
			||||||
 | 
					      "signed_from": ""
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: GET
 | 
				
			||||||
 | 
					name: Verify Something
 | 
				
			||||||
 | 
					sortPriority: 6000.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/signature/verify
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										48
									
								
								.yaak/yaak.rq_A81dnTkhL5.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.yaak/yaak.rq_A81dnTkhL5.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_A81dnTkhL5
 | 
				
			||||||
 | 
					createdAt: 2024-12-31T10:05:25
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:28:59.911928
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "user_id": "",
 | 
				
			||||||
 | 
					      "surname": "",
 | 
				
			||||||
 | 
					      "status_flag": "Deleted",
 | 
				
			||||||
 | 
					      "name": "",
 | 
				
			||||||
 | 
					      "permissions": [
 | 
				
			||||||
 | 
					        "Test"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "active_directory_auth": true,
 | 
				
			||||||
 | 
					      "email": "",
 | 
				
			||||||
 | 
					      "group_permissions": []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: GET
 | 
				
			||||||
 | 
					name: Users
 | 
				
			||||||
 | 
					sortPriority: 2000.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/users
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										35
									
								
								.yaak/yaak.rq_Eb5hJAiaKG.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.yaak/yaak.rq_Eb5hJAiaKG.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_Eb5hJAiaKG
 | 
				
			||||||
 | 
					createdAt: 2024-12-01T08:39:12
 | 
				
			||||||
 | 
					updatedAt: 2025-03-02T07:37:25.323717
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication: {}
 | 
				
			||||||
 | 
					authenticationType: null
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "TestUser",
 | 
				
			||||||
 | 
					      "password": "test"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: POST
 | 
				
			||||||
 | 
					name: Auth
 | 
				
			||||||
 | 
					sortPriority: 0.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/login
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										39
									
								
								.yaak/yaak.rq_FJT5RHtZ2B.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.yaak/yaak.rq_FJT5RHtZ2B.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_FJT5RHtZ2B
 | 
				
			||||||
 | 
					createdAt: 2025-04-05T16:17:53.461290
 | 
				
			||||||
 | 
					updatedAt: 2025-04-05T16:52:50.453961
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "draft_id": "2015-09-05 23:56:04"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: FWIGbxmTAH
 | 
				
			||||||
 | 
					method: GET
 | 
				
			||||||
 | 
					name: Get attached Draft Files
 | 
				
			||||||
 | 
					sortPriority: 6000.002
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/files/draft
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										40
									
								
								.yaak/yaak.rq_FmsM38yvHM.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.yaak/yaak.rq_FmsM38yvHM.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_FmsM38yvHM
 | 
				
			||||||
 | 
					createdAt: 2025-01-06T10:53:14
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:29:12.196535
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "secret": "EinTestSecret",
 | 
				
			||||||
 | 
					      "ttl": 30
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: PUT
 | 
				
			||||||
 | 
					name: Verify Signing Key
 | 
				
			||||||
 | 
					sortPriority: 4000.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/signature/key
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										48
									
								
								.yaak/yaak.rq_G6OxWcR5j2.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.yaak/yaak.rq_G6OxWcR5j2.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_G6OxWcR5j2
 | 
				
			||||||
 | 
					createdAt: 2025-01-06T10:50:59
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:29:16.792639
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "user_id": "",
 | 
				
			||||||
 | 
					      "surname": "",
 | 
				
			||||||
 | 
					      "status_flag": "Deleted",
 | 
				
			||||||
 | 
					      "name": "",
 | 
				
			||||||
 | 
					      "permissions": [
 | 
				
			||||||
 | 
					        "Test"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "active_directory_auth": true,
 | 
				
			||||||
 | 
					      "email": "",
 | 
				
			||||||
 | 
					      "group_permissions": []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: GET
 | 
				
			||||||
 | 
					name: Verify Signing Key
 | 
				
			||||||
 | 
					sortPriority: 4500.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/signature/key
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										39
									
								
								.yaak/yaak.rq_SFA4nX8S0k.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.yaak/yaak.rq_SFA4nX8S0k.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_SFA4nX8S0k
 | 
				
			||||||
 | 
					createdAt: 2025-01-06T10:55:51
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:29:07.550911
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "secret": "EinTestSecret"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: POST
 | 
				
			||||||
 | 
					name: Create Signing Key
 | 
				
			||||||
 | 
					sortPriority: 3000.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/signature/key
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										40
									
								
								.yaak/yaak.rq_eFkZk6Z6QS.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.yaak/yaak.rq_eFkZk6Z6QS.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_eFkZk6Z6QS
 | 
				
			||||||
 | 
					createdAt: 2025-04-05T16:52:35.932090
 | 
				
			||||||
 | 
					updatedAt: 2025-04-05T16:55:08.986210
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "draft_id": "2015-09-05 23:56:04",
 | 
				
			||||||
 | 
					      "hash": "9CE71B15548BEAC9745F464531B70BEE30F71F310908D3A0A86E73E07F61ED61"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: FWIGbxmTAH
 | 
				
			||||||
 | 
					method: GET
 | 
				
			||||||
 | 
					name: Download Draft File
 | 
				
			||||||
 | 
					sortPriority: 6000.003
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/files/draft/file
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										48
									
								
								.yaak/yaak.rq_gU10vzCIxt.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.yaak/yaak.rq_gU10vzCIxt.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_gU10vzCIxt
 | 
				
			||||||
 | 
					createdAt: 2025-01-04T18:07:52
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:28:46.667681
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "user_id": "",
 | 
				
			||||||
 | 
					      "surname": "",
 | 
				
			||||||
 | 
					      "status_flag": "Deleted",
 | 
				
			||||||
 | 
					      "name": "",
 | 
				
			||||||
 | 
					      "permissions": [
 | 
				
			||||||
 | 
					        "Test"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "active_directory_auth": true,
 | 
				
			||||||
 | 
					      "email": "",
 | 
				
			||||||
 | 
					      "group_permissions": []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: GET
 | 
				
			||||||
 | 
					name: Api Keys
 | 
				
			||||||
 | 
					sortPriority: 1000.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/apikeys
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										39
									
								
								.yaak/yaak.rq_lGuHoFSNGa.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.yaak/yaak.rq_lGuHoFSNGa.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_lGuHoFSNGa
 | 
				
			||||||
 | 
					createdAt: 2025-01-06T10:56:17
 | 
				
			||||||
 | 
					updatedAt: 2025-03-31T17:29:20.133348
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  text: |-
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "secret": "EinTestSecret"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					bodyType: application/json
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: application/json
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					method: POST
 | 
				
			||||||
 | 
					name: Sign Something
 | 
				
			||||||
 | 
					sortPriority: 5000.0
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/signature/sign
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										52
									
								
								.yaak/yaak.rq_t5k4XC8Poe.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								.yaak/yaak.rq_t5k4XC8Poe.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					type: http_request
 | 
				
			||||||
 | 
					model: http_request
 | 
				
			||||||
 | 
					id: rq_t5k4XC8Poe
 | 
				
			||||||
 | 
					createdAt: 2025-04-02T16:41:11.369047
 | 
				
			||||||
 | 
					updatedAt: 2025-04-05T17:00:29.492513
 | 
				
			||||||
 | 
					workspaceId: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					folderId: fl_1twfKS3Z0A
 | 
				
			||||||
 | 
					authentication:
 | 
				
			||||||
 | 
					  token: ${[ response.body.raw(request='rq_Eb5hJAiaKG') ]}
 | 
				
			||||||
 | 
					authenticationType: bearer
 | 
				
			||||||
 | 
					body:
 | 
				
			||||||
 | 
					  form:
 | 
				
			||||||
 | 
					  - enabled: true
 | 
				
			||||||
 | 
					    file: ''
 | 
				
			||||||
 | 
					    id: u6yikyzRTH
 | 
				
			||||||
 | 
					    name: file
 | 
				
			||||||
 | 
					  - enabled: true
 | 
				
			||||||
 | 
					    id: njtBaKefpN
 | 
				
			||||||
 | 
					    name: name
 | 
				
			||||||
 | 
					    value: '"TestValue"'
 | 
				
			||||||
 | 
					  - enabled: true
 | 
				
			||||||
 | 
					    id: Iav8TimHS8
 | 
				
			||||||
 | 
					    name: draft_id
 | 
				
			||||||
 | 
					    value: 2015-09-05 23:56:04
 | 
				
			||||||
 | 
					  - enabled: true
 | 
				
			||||||
 | 
					    id: d57ZzimswD
 | 
				
			||||||
 | 
					    name: ''
 | 
				
			||||||
 | 
					    value: ''
 | 
				
			||||||
 | 
					bodyType: multipart/form-data
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					headers:
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: x-api-key
 | 
				
			||||||
 | 
					  value: ${[ ApiKey ]}
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: false
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: ''
 | 
				
			||||||
 | 
					  value: ''
 | 
				
			||||||
 | 
					  id: null
 | 
				
			||||||
 | 
					- enabled: true
 | 
				
			||||||
 | 
					  name: Content-Type
 | 
				
			||||||
 | 
					  value: multipart/form-data
 | 
				
			||||||
 | 
					  id: rf64n05TdT
 | 
				
			||||||
 | 
					method: POST
 | 
				
			||||||
 | 
					name: Upload Draft File
 | 
				
			||||||
 | 
					sortPriority: 6000.001
 | 
				
			||||||
 | 
					url: http://localhost:8080/api/files/draft
 | 
				
			||||||
 | 
					urlParameters: []
 | 
				
			||||||
							
								
								
									
										10
									
								
								.yaak/yaak.wk_SlydsyH2WI.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.yaak/yaak.wk_SlydsyH2WI.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					type: workspace
 | 
				
			||||||
 | 
					model: workspace
 | 
				
			||||||
 | 
					id: wk_SlydsyH2WI
 | 
				
			||||||
 | 
					createdAt: 2024-12-01T08:38:45
 | 
				
			||||||
 | 
					updatedAt: 2024-12-01T08:38:45
 | 
				
			||||||
 | 
					name: Yaak
 | 
				
			||||||
 | 
					description: ''
 | 
				
			||||||
 | 
					settingValidateCertificates: true
 | 
				
			||||||
 | 
					settingFollowRedirects: true
 | 
				
			||||||
 | 
					settingRequestTimeout: 0
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
use axum_jwt_login::{AuthBackend, UserPermissions};
 | 
					use axum_jwt_login::{AuthBackend, UserPermissions};
 | 
				
			||||||
use ldap::LDAPBackend;
 | 
					use ldap::LDAPBackend;
 | 
				
			||||||
 | 
					use private_key_cache::PrivateKeyCache;
 | 
				
			||||||
use sqlx::PgPool;
 | 
					use sqlx::PgPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
@ -11,11 +12,13 @@ use crate::{
 | 
				
			|||||||
use super::routes::{auth::models::Credentials, users::models::User};
 | 
					use super::routes::{auth::models::Credentials, users::models::User};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod ldap;
 | 
					pub mod ldap;
 | 
				
			||||||
 | 
					pub mod private_key_cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
pub struct ApiBackend {
 | 
					pub struct ApiBackend {
 | 
				
			||||||
    pool: PgPool,
 | 
					    pool: PgPool,
 | 
				
			||||||
    config: Configuration,
 | 
					    config: Configuration,
 | 
				
			||||||
 | 
					    private_key_cache: PrivateKeyCache,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ApiBackend {
 | 
					impl ApiBackend {
 | 
				
			||||||
@ -23,6 +26,7 @@ impl ApiBackend {
 | 
				
			|||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            pool,
 | 
					            pool,
 | 
				
			||||||
            config: config.clone(),
 | 
					            config: config.clone(),
 | 
				
			||||||
 | 
					            private_key_cache: PrivateKeyCache::new(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,6 +37,10 @@ impl ApiBackend {
 | 
				
			|||||||
    pub fn config(&self) -> &Configuration {
 | 
					    pub fn config(&self) -> &Configuration {
 | 
				
			||||||
        &self.config
 | 
					        &self.config
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn private_key_cache(&self) -> PrivateKeyCache {
 | 
				
			||||||
 | 
					        self.private_key_cache.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl AuthBackend<User> for ApiBackend {
 | 
					impl AuthBackend<User> for ApiBackend {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										77
									
								
								src/api/backend/private_key_cache.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/api/backend/private_key_cache.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    collections::HashMap,
 | 
				
			||||||
 | 
					    sync::Arc,
 | 
				
			||||||
 | 
					    time::{Duration, Instant},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use minisign::SecretKey;
 | 
				
			||||||
 | 
					use tokio::sync::Mutex;
 | 
				
			||||||
 | 
					use tokio_util::sync::CancellationToken;
 | 
				
			||||||
 | 
					use tracing::debug;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct PrivateKeyCache {
 | 
				
			||||||
 | 
					    cache: Arc<Mutex<HashMap<String, PrivateKeyEntry>>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct PrivateKeyEntry {
 | 
				
			||||||
 | 
					    key: SecretKey,
 | 
				
			||||||
 | 
					    ttl: Duration,
 | 
				
			||||||
 | 
					    creation: Instant,
 | 
				
			||||||
 | 
					    clean_token: CancellationToken,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl PrivateKeyEntry {
 | 
				
			||||||
 | 
					    pub fn secret_key(&self) -> SecretKey {
 | 
				
			||||||
 | 
					        self.key.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl PrivateKeyCache {
 | 
				
			||||||
 | 
					    pub fn new() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            cache: Arc::new(Mutex::new(HashMap::new())),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn add_key(&self, user: &str, key: &SecretKey, ttl: Duration) {
 | 
				
			||||||
 | 
					        // create token to abort cleaning task
 | 
				
			||||||
 | 
					        let clean_token = CancellationToken::new();
 | 
				
			||||||
 | 
					        // create private key entry
 | 
				
			||||||
 | 
					        let entry = PrivateKeyEntry {
 | 
				
			||||||
 | 
					            key: key.clone(),
 | 
				
			||||||
 | 
					            ttl,
 | 
				
			||||||
 | 
					            creation: Instant::now(),
 | 
				
			||||||
 | 
					            clean_token: clean_token.clone(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        // lock cache
 | 
				
			||||||
 | 
					        let mut cache = self.cache.lock().await;
 | 
				
			||||||
 | 
					        // add private key to cache
 | 
				
			||||||
 | 
					        if let Some(old_entry) = cache.insert(user.to_string(), entry) {
 | 
				
			||||||
 | 
					            // abort cleaning task of old entry
 | 
				
			||||||
 | 
					            old_entry.clean_token.cancel();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // spawn task to remove private key from cache after storage expiration
 | 
				
			||||||
 | 
					        let cache = self.cache.clone();
 | 
				
			||||||
 | 
					        let user = user.to_string();
 | 
				
			||||||
 | 
					        tokio::spawn(async move {
 | 
				
			||||||
 | 
					            tokio::select! {
 | 
				
			||||||
 | 
					                _ = tokio::time::sleep(ttl) => {
 | 
				
			||||||
 | 
					                    let mut lock = cache.lock().await;
 | 
				
			||||||
 | 
					                    lock.remove(&user);
 | 
				
			||||||
 | 
					                    debug!("Deleted cached Secret Key after TTL expiration")
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                _ = clean_token.cancelled() => {
 | 
				
			||||||
 | 
					                    debug!("Cancelled automated cache clearance after TTL renewal")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn get_key(&self, user_id: &str) -> Option<PrivateKeyEntry> {
 | 
				
			||||||
 | 
					        let lock = self.cache.lock().await;
 | 
				
			||||||
 | 
					        lock.get(user_id).cloned()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -8,12 +8,14 @@ pub const USERS_TAG: &str = "Users";
 | 
				
			|||||||
pub const ORDER_TAG: &str = "order";
 | 
					pub const ORDER_TAG: &str = "order";
 | 
				
			||||||
pub const API_KEY_TAG: &str = "API Keys";
 | 
					pub const API_KEY_TAG: &str = "API Keys";
 | 
				
			||||||
pub const SIGNATURE_TAG: &str = "Signature";
 | 
					pub const SIGNATURE_TAG: &str = "Signature";
 | 
				
			||||||
 | 
					pub const FILE_TAG: &str = "Files";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(OpenApi)]
 | 
					#[derive(OpenApi)]
 | 
				
			||||||
#[openapi(
 | 
					#[openapi(
 | 
				
			||||||
    modifiers(&SecurityAddon),
 | 
					    modifiers(&SecurityAddon),
 | 
				
			||||||
    tags(
 | 
					    tags(
 | 
				
			||||||
        (name = AUTH_TAG, description = "API Authentication endpoints"),
 | 
					        (name = AUTH_TAG, description = "API Authentication endpoints"),
 | 
				
			||||||
 | 
					        (name = FILE_TAG, description = "Upload and Download Files"),
 | 
				
			||||||
        (name = ORDER_TAG, description = "Order API endpoints")
 | 
					        (name = ORDER_TAG, description = "Order API endpoints")
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								src/api/routes/api_keys/handlers/apikeys_delete.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/api/routes/api_keys/handlers/apikeys_delete.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, extract::Query, Extension};
 | 
				
			||||||
 | 
					use serde::Deserialize;
 | 
				
			||||||
 | 
					use utoipa::IntoParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{backend::ApiBackend, description::API_KEY_TAG, routes::api_keys::sql},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Deserialize, IntoParams)]
 | 
				
			||||||
 | 
					pub struct DeleteQueryParameters {
 | 
				
			||||||
 | 
					    key_id: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    delete,
 | 
				
			||||||
 | 
					    path = "/apikeys",
 | 
				
			||||||
 | 
					    summary = "Delete API Key",
 | 
				
			||||||
 | 
					    description = "Delete an API Key.",
 | 
				
			||||||
 | 
					    params(DeleteQueryParameters),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "API Key successfully deleted"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:apikeys",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = API_KEY_TAG)]
 | 
				
			||||||
 | 
					pub async fn delete_api_key(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					    Query(params): Query<DeleteQueryParameters>,
 | 
				
			||||||
 | 
					) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    sql::delete_api_key(backend.pool(), ¶ms.key_id).await?;
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/api/routes/api_keys/handlers/apikeys_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/api/routes/api_keys/handlers/apikeys_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Extension, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        backend::ApiBackend,
 | 
				
			||||||
 | 
					        description::API_KEY_TAG,
 | 
				
			||||||
 | 
					        routes::api_keys::{models::ApiKey, sql},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/apikeys",
 | 
				
			||||||
 | 
					    summary = "Get all API Keys",
 | 
				
			||||||
 | 
					    description = "Get a list of all configured API Keys.",
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = Vec<ApiKey>, description = "List of API Keys"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["read:apikeys",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = API_KEY_TAG)]
 | 
				
			||||||
 | 
					pub async fn get_api_keys(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					) -> Result<Json<Vec<ApiKey>>, ApiError> {
 | 
				
			||||||
 | 
					    Ok(Json(sql::get_api_keys(backend.pool()).await?))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/api/routes/api_keys/handlers/apikeys_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/api/routes/api_keys/handlers/apikeys_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        description::API_KEY_TAG,
 | 
				
			||||||
 | 
					        routes::{
 | 
				
			||||||
 | 
					            api_keys::{models::ApiKey, sql},
 | 
				
			||||||
 | 
					            AuthBackendType,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/apikeys",
 | 
				
			||||||
 | 
					    summary = "Create new API Key",
 | 
				
			||||||
 | 
					    description = "Create a new API Key.",
 | 
				
			||||||
 | 
					    request_body(content = ApiKey, description = "API Key details", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "API Key successfully created (API Key Secret in Body)", body = String),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:apikeys",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = API_KEY_TAG)]
 | 
				
			||||||
 | 
					pub async fn create_api_key(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(api_key): Json<ApiKey>,
 | 
				
			||||||
 | 
					) -> Result<String, ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create new API key
 | 
				
			||||||
 | 
					    let (key_secret, key) = ApiKey::create(
 | 
				
			||||||
 | 
					        &api_key.name,
 | 
				
			||||||
 | 
					        api_key.auth_required,
 | 
				
			||||||
 | 
					        api_key.permissions.0,
 | 
				
			||||||
 | 
					        backend.config(),
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // insert API Key into database
 | 
				
			||||||
 | 
					    sql::create_api_key(backend.pool(), &key).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // add API Key to session
 | 
				
			||||||
 | 
					    auth_session.add_api_key(key).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Return key secret in response
 | 
				
			||||||
 | 
					    Ok(key_secret)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/api/routes/api_keys/handlers/apikeys_put.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/api/routes/api_keys/handlers/apikeys_put.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Extension, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        backend::ApiBackend,
 | 
				
			||||||
 | 
					        description::API_KEY_TAG,
 | 
				
			||||||
 | 
					        routes::api_keys::{models::ApiKey, sql},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    put,
 | 
				
			||||||
 | 
					    path = "/apikeys",
 | 
				
			||||||
 | 
					    summary = "Update API Key",
 | 
				
			||||||
 | 
					    description = "Update an API Key.",
 | 
				
			||||||
 | 
					    request_body(content = ApiKey, description = "API Key details", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "API Key successfully updated"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:apikeys",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = API_KEY_TAG)]
 | 
				
			||||||
 | 
					pub async fn update_api_key(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					    Json(api_key): Json<ApiKey>,
 | 
				
			||||||
 | 
					) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    sql::update_api_key(backend.pool(), &api_key).await?;
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								src/api/routes/api_keys/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/api/routes/api_keys/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					pub mod apikeys_delete;
 | 
				
			||||||
 | 
					pub mod apikeys_get;
 | 
				
			||||||
 | 
					pub mod apikeys_post;
 | 
				
			||||||
 | 
					pub mod apikeys_put;
 | 
				
			||||||
@ -1,24 +1,15 @@
 | 
				
			|||||||
use axum::{debug_handler, extract::Query, Extension, Json};
 | 
					 | 
				
			||||||
use models::ApiKey;
 | 
					 | 
				
			||||||
use serde::Deserialize;
 | 
					 | 
				
			||||||
use utoipa::IntoParams;
 | 
					 | 
				
			||||||
use utoipa_axum::{router::OpenApiRouter, routes};
 | 
					use utoipa_axum::{router::OpenApiRouter, routes};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    api::{
 | 
					    api::routes::users::permissions::{Permission, PermissionDetail},
 | 
				
			||||||
        backend::ApiBackend,
 | 
					 | 
				
			||||||
        description::API_KEY_TAG,
 | 
					 | 
				
			||||||
        routes::{
 | 
					 | 
				
			||||||
            users::permissions::{Permission, PermissionDetail},
 | 
					 | 
				
			||||||
            User,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    errors::ApiError,
 | 
					 | 
				
			||||||
    permission_required,
 | 
					    permission_required,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					use handlers::apikeys_delete::{__path_delete_api_key, delete_api_key};
 | 
				
			||||||
 | 
					use handlers::apikeys_get::{__path_get_api_keys, get_api_keys};
 | 
				
			||||||
 | 
					use handlers::apikeys_post::{__path_create_api_key, create_api_key};
 | 
				
			||||||
 | 
					use handlers::apikeys_put::{__path_update_api_key, update_api_key};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::AuthBackendType;
 | 
					mod handlers;
 | 
				
			||||||
 | 
					 | 
				
			||||||
pub mod models;
 | 
					pub mod models;
 | 
				
			||||||
pub mod sql;
 | 
					pub mod sql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,109 +28,3 @@ pub fn router() -> OpenApiRouter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    OpenApiRouter::new().merge(read).merge(write)
 | 
					    OpenApiRouter::new().merge(read).merge(write)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    get,
 | 
					 | 
				
			||||||
    path = "/apikeys",
 | 
					 | 
				
			||||||
    summary = "Get all API Keys",
 | 
					 | 
				
			||||||
    description = "Get a list of all configured API Keys.",
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, body = Vec<ApiKey>, description = "List of API Keys"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["read:apikeys",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = API_KEY_TAG)]
 | 
					 | 
				
			||||||
pub async fn get_api_keys(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
) -> Result<Json<Vec<ApiKey>>, ApiError> {
 | 
					 | 
				
			||||||
    Ok(Json(sql::get_api_keys(backend.pool()).await?))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    post,
 | 
					 | 
				
			||||||
    path = "/apikeys",
 | 
					 | 
				
			||||||
    summary = "Create new API Key",
 | 
					 | 
				
			||||||
    description = "Create a new API Key.",
 | 
					 | 
				
			||||||
    request_body(content = ApiKey, description = "API Key details", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "API Key successfully created (API Key Secret in Body)", body = String),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:apikeys",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = API_KEY_TAG)]
 | 
					 | 
				
			||||||
pub async fn create_api_key(
 | 
					 | 
				
			||||||
    auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
    Json(api_key): Json<ApiKey>,
 | 
					 | 
				
			||||||
) -> Result<String, ApiError> {
 | 
					 | 
				
			||||||
    let backend = auth_session.backend();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // create new API key
 | 
					 | 
				
			||||||
    let (key_secret, key) = ApiKey::create(
 | 
					 | 
				
			||||||
        &api_key.name,
 | 
					 | 
				
			||||||
        api_key.auth_required,
 | 
					 | 
				
			||||||
        api_key.permissions.0,
 | 
					 | 
				
			||||||
        backend.config(),
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // insert API Key into database
 | 
					 | 
				
			||||||
    sql::create_api_key(backend.pool(), &key).await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // add API Key to session
 | 
					 | 
				
			||||||
    auth_session.add_api_key(key).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Return key secret in response
 | 
					 | 
				
			||||||
    Ok(key_secret)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    put,
 | 
					 | 
				
			||||||
    path = "/apikeys",
 | 
					 | 
				
			||||||
    summary = "Update API Key",
 | 
					 | 
				
			||||||
    description = "Update an API Key.",
 | 
					 | 
				
			||||||
    request_body(content = ApiKey, description = "API Key details", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "API Key successfully updated"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:apikeys",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = API_KEY_TAG)]
 | 
					 | 
				
			||||||
pub async fn update_api_key(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
    Json(api_key): Json<ApiKey>,
 | 
					 | 
				
			||||||
) -> Result<(), ApiError> {
 | 
					 | 
				
			||||||
    sql::update_api_key(backend.pool(), &api_key).await?;
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Deserialize, IntoParams)]
 | 
					 | 
				
			||||||
pub struct DeleteQueryParameters {
 | 
					 | 
				
			||||||
    key_id: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    delete,
 | 
					 | 
				
			||||||
    path = "/apikeys",
 | 
					 | 
				
			||||||
    summary = "Delete API Key",
 | 
					 | 
				
			||||||
    description = "Delete an API Key.",
 | 
					 | 
				
			||||||
    params(DeleteQueryParameters),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "API Key successfully deleted"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:apikeys",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = API_KEY_TAG)]
 | 
					 | 
				
			||||||
pub async fn delete_api_key(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
    Query(params): Query<DeleteQueryParameters>,
 | 
					 | 
				
			||||||
) -> Result<(), ApiError> {
 | 
					 | 
				
			||||||
    sql::delete_api_key(backend.pool(), ¶ms.key_id).await?;
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										40
									
								
								src/api/routes/auth/handlers/login_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/api/routes/auth/handlers/login_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        description::AUTH_TAG,
 | 
				
			||||||
 | 
					        routes::{auth::models::Credentials, AuthBackendType},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/login",
 | 
				
			||||||
 | 
					    summary = "Authenticate as user",
 | 
				
			||||||
 | 
					    description = "Authenticate as user and receive a JWT auth token.",
 | 
				
			||||||
 | 
					    request_body(content = Credentials, description = "User credentials", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = String, description = "Successfully logged in (JWT Token in body)"),
 | 
				
			||||||
 | 
					        (status = UNAUTHORIZED, description = "Invalid credentials or unauthorized user")
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					     tag = AUTH_TAG)]
 | 
				
			||||||
 | 
					pub async fn authorize(
 | 
				
			||||||
 | 
					    mut auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(credentials): Json<Credentials>,
 | 
				
			||||||
 | 
					) -> Result<String, ApiError> {
 | 
				
			||||||
 | 
					    let token = match auth_session.authenticate(credentials).await {
 | 
				
			||||||
 | 
					        Ok(Some(_user)) => {
 | 
				
			||||||
 | 
					            if let Some(token) = auth_session.get_auth_token() {
 | 
				
			||||||
 | 
					                token
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return Err(ApiError::InvalidCredentials);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(None) => return Err(ApiError::InvalidCredentials),
 | 
				
			||||||
 | 
					        Err(_) => return Err(ApiError::InvalidCredentials),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(token)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/api/routes/auth/handlers/logout_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/api/routes/auth/handlers/logout_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					use axum::debug_handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{description::AUTH_TAG, routes::AuthBackendType},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/logout",
 | 
				
			||||||
 | 
					    summary = "Logout",
 | 
				
			||||||
 | 
					    description = "Log the currently logged in user out.",
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "Logout successful")
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					     tag = AUTH_TAG)]
 | 
				
			||||||
 | 
					pub async fn logout(mut auth_session: AuthBackendType) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    auth_session.logout().await?;
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/api/routes/auth/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/api/routes/auth/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					pub mod login_post;
 | 
				
			||||||
 | 
					pub mod logout_post;
 | 
				
			||||||
@ -1,11 +1,9 @@
 | 
				
			|||||||
use axum::{debug_handler, Json};
 | 
					 | 
				
			||||||
use models::Credentials;
 | 
					 | 
				
			||||||
use utoipa_axum::{router::OpenApiRouter, routes};
 | 
					use utoipa_axum::{router::OpenApiRouter, routes};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{api::description::AUTH_TAG, errors::ApiError};
 | 
					use crate::api::routes::auth::handlers::login_post::{__path_authorize, authorize};
 | 
				
			||||||
 | 
					use crate::api::routes::auth::handlers::logout_post::{__path_logout, logout};
 | 
				
			||||||
use super::AuthBackendType;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod handlers;
 | 
				
			||||||
pub mod models;
 | 
					pub mod models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// expose the OpenAPI to parent module
 | 
					// expose the OpenAPI to parent module
 | 
				
			||||||
@ -14,49 +12,3 @@ pub fn router() -> OpenApiRouter {
 | 
				
			|||||||
        .routes(routes!(authorize))
 | 
					        .routes(routes!(authorize))
 | 
				
			||||||
        .routes(routes!(logout))
 | 
					        .routes(routes!(logout))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    post,
 | 
					 | 
				
			||||||
    path = "/login",
 | 
					 | 
				
			||||||
    summary = "Authenticate as user",
 | 
					 | 
				
			||||||
    description = "Authenticate as user and receive a JWT auth token.",
 | 
					 | 
				
			||||||
    request_body(content = Credentials, description = "User credentials", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, body = String, description = "Successfully logged in (JWT Token in body)"),
 | 
					 | 
				
			||||||
        (status = UNAUTHORIZED, description = "Invalid credentials or unauthorized user")
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
     tag = AUTH_TAG)]
 | 
					 | 
				
			||||||
pub async fn authorize(
 | 
					 | 
				
			||||||
    mut auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
    Json(credentials): Json<Credentials>,
 | 
					 | 
				
			||||||
) -> Result<String, ApiError> {
 | 
					 | 
				
			||||||
    let token = match auth_session.authenticate(credentials).await {
 | 
					 | 
				
			||||||
        Ok(Some(_user)) => {
 | 
					 | 
				
			||||||
            if let Some(token) = auth_session.get_auth_token() {
 | 
					 | 
				
			||||||
                token
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return Err(ApiError::InvalidCredentials);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(None) => return Err(ApiError::InvalidCredentials),
 | 
					 | 
				
			||||||
        Err(_) => return Err(ApiError::InvalidCredentials),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(token)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    post,
 | 
					 | 
				
			||||||
    path = "/logout",
 | 
					 | 
				
			||||||
    summary = "Logout",
 | 
					 | 
				
			||||||
    description = "Log the currently logged in user out.",
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "Logout successful")
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
     tag = AUTH_TAG)]
 | 
					 | 
				
			||||||
pub async fn logout(mut auth_session: AuthBackendType) -> Result<(), ApiError> {
 | 
					 | 
				
			||||||
    auth_session.logout().await?;
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										64
									
								
								src/api/routes/files/handlers/files_draft_file_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/api/routes/files/handlers/files_draft_file_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    body::Body,
 | 
				
			||||||
 | 
					    debug_handler,
 | 
				
			||||||
 | 
					    http::header,
 | 
				
			||||||
 | 
					    response::{IntoResponse, Response},
 | 
				
			||||||
 | 
					    Json,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					use serde::Deserialize;
 | 
				
			||||||
 | 
					use ts_rs::TS;
 | 
				
			||||||
 | 
					use utoipa::ToSchema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        description::FILE_TAG,
 | 
				
			||||||
 | 
					        routes::{files::sql, AuthBackendType},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Deserialize, TS, ToSchema)]
 | 
				
			||||||
 | 
					#[ts(export)]
 | 
				
			||||||
 | 
					pub struct GetFileRequest {
 | 
				
			||||||
 | 
					    draft_id: String,
 | 
				
			||||||
 | 
					    hash: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/files/draft/file",
 | 
				
			||||||
 | 
					    summary = "Download specified file",
 | 
				
			||||||
 | 
					    description = "Download specified file.",
 | 
				
			||||||
 | 
					    request_body(content = GetFileRequest, description = "Request to download specified file", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "File Data", content_type = "application/octet-stream"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					     tag = FILE_TAG)]
 | 
				
			||||||
 | 
					pub async fn get_specified_draft_file(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(request): Json<GetFileRequest>,
 | 
				
			||||||
 | 
					) -> Result<impl IntoResponse, ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let draft_id =
 | 
				
			||||||
 | 
					        NaiveDateTime::parse_from_str(&request.draft_id, "%Y-%m-%d %H:%M:%S").map_err(|_| {
 | 
				
			||||||
 | 
					            ApiError::InvalidRequest("Could not parse NaiveDateTime from draft id".to_string())
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match sql::get_specified_draft_file(backend.pool(), user, draft_id, request.hash).await? {
 | 
				
			||||||
 | 
					        Some(file) => Ok(Response::builder()
 | 
				
			||||||
 | 
					            .header(header::CONTENT_TYPE, file.content_type)
 | 
				
			||||||
 | 
					            .header(
 | 
				
			||||||
 | 
					                header::CONTENT_DISPOSITION,
 | 
				
			||||||
 | 
					                format!("attachment; filename=\"{}\"", file.name),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .body(Body::from(file.data))
 | 
				
			||||||
 | 
					            .unwrap_or_default()),
 | 
				
			||||||
 | 
					        None => Err(ApiError::FileNotFound),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/api/routes/files/handlers/files_draft_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/api/routes/files/handlers/files_draft_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					use serde::Deserialize;
 | 
				
			||||||
 | 
					use ts_rs::TS;
 | 
				
			||||||
 | 
					use utoipa::ToSchema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        description::FILE_TAG,
 | 
				
			||||||
 | 
					        routes::{
 | 
				
			||||||
 | 
					            files::{models::AttachedFile, sql},
 | 
				
			||||||
 | 
					            AuthBackendType,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Deserialize, TS, ToSchema)]
 | 
				
			||||||
 | 
					#[ts(export)]
 | 
				
			||||||
 | 
					pub struct GetAttachedFilesRequest {
 | 
				
			||||||
 | 
					    draft_id: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/files/draft",
 | 
				
			||||||
 | 
					    summary = "Get Files attached to Draft",
 | 
				
			||||||
 | 
					    description = "Get list of all files that are attached to a specified draft.",
 | 
				
			||||||
 | 
					    request_body(content = GetAttachedFilesRequest, description = "Request for attached files", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = AttachedFile, description = "Attached Files List", content_type = "application/json"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					     tag = FILE_TAG)]
 | 
				
			||||||
 | 
					pub async fn get_attached_draft_files(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(request): Json<GetAttachedFilesRequest>,
 | 
				
			||||||
 | 
					) -> Result<Json<Vec<AttachedFile>>, ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let draft_id =
 | 
				
			||||||
 | 
					        NaiveDateTime::parse_from_str(&request.draft_id, "%Y-%m-%d %H:%M:%S").map_err(|_| {
 | 
				
			||||||
 | 
					            ApiError::InvalidRequest("Could not parse NaiveDateTime from draft id".to_string())
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
 | 
					    Ok(Json(
 | 
				
			||||||
 | 
					        sql::get_draft_attached_files(backend.pool(), user, draft_id).await?,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								src/api/routes/files/handlers/files_draft_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/api/routes/files/handlers/files_draft_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, extract::Multipart, http::StatusCode};
 | 
				
			||||||
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					use serde::Deserialize;
 | 
				
			||||||
 | 
					use tokio_util::bytes::Bytes;
 | 
				
			||||||
 | 
					use ts_rs::TS;
 | 
				
			||||||
 | 
					use utoipa::ToSchema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        description::FILE_TAG,
 | 
				
			||||||
 | 
					        routes::{
 | 
				
			||||||
 | 
					            files::{models::File, sql},
 | 
				
			||||||
 | 
					            AuthBackendType,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::super::FILE_SIZE_LIMIT_MB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Deserialize, TS, ToSchema)]
 | 
				
			||||||
 | 
					#[ts(export)]
 | 
				
			||||||
 | 
					#[allow(unused)]
 | 
				
			||||||
 | 
					pub struct FileUploadRequest {
 | 
				
			||||||
 | 
					    name: Option<String>,
 | 
				
			||||||
 | 
					    #[schema(value_type = String)]
 | 
				
			||||||
 | 
					    draft_id: NaiveDateTime,
 | 
				
			||||||
 | 
					    #[schema(format = Binary, content_media_type = "application/octet-stream")]
 | 
				
			||||||
 | 
					    file: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/files/draft",
 | 
				
			||||||
 | 
					    summary = "Draft File Upload",
 | 
				
			||||||
 | 
					    description = "Upload a file as draft file.",
 | 
				
			||||||
 | 
					    request_body(content = FileUploadRequest, description = "File Data", content_type = "multipart/form-data"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = String, description = "Successfully uploaded and stored file"),
 | 
				
			||||||
 | 
					        (status = 413, description = format!("The size of the uploaded file is too large (max {FILE_SIZE_LIMIT_MB} MB)"))
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					     tag = FILE_TAG)]
 | 
				
			||||||
 | 
					pub async fn upload_draft_file(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    mut multipart: Multipart,
 | 
				
			||||||
 | 
					) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut name: Option<String> = None;
 | 
				
			||||||
 | 
					    let mut draft_id: Option<NaiveDateTime> = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut content_type: Option<String> = None;
 | 
				
			||||||
 | 
					    let mut file_name: Option<String> = None;
 | 
				
			||||||
 | 
					    let mut bytes: Option<Bytes> = None;
 | 
				
			||||||
 | 
					    let mut size: Option<i32> = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while let Some(field) = multipart.next_field().await.unwrap() {
 | 
				
			||||||
 | 
					        let field_name = field.name();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match &field_name {
 | 
				
			||||||
 | 
					            Some("name") => name = Some(field.text().await?),
 | 
				
			||||||
 | 
					            Some("draft_id") => {
 | 
				
			||||||
 | 
					                draft_id = Some(
 | 
				
			||||||
 | 
					                    NaiveDateTime::parse_from_str(&field.text().await?, "%Y-%m-%d %H:%M:%S")
 | 
				
			||||||
 | 
					                        .map_err(|_| {
 | 
				
			||||||
 | 
					                            ApiError::InvalidRequest(
 | 
				
			||||||
 | 
					                                "Could not parse NaiveDateTime from draft id".to_string(),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        })?,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Some("file") => {
 | 
				
			||||||
 | 
					                file_name = field.file_name().map(ToString::to_string);
 | 
				
			||||||
 | 
					                content_type = field.content_type().map(ToString::to_string);
 | 
				
			||||||
 | 
					                let _bytes = field.bytes().await?;
 | 
				
			||||||
 | 
					                size = Some(_bytes.len() as i32);
 | 
				
			||||||
 | 
					                bytes = Some(_bytes);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => (),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // store file in database
 | 
				
			||||||
 | 
					    if let (Some(data), Some(draft_id), Some(content_type), Some(filename)) =
 | 
				
			||||||
 | 
					        (bytes, draft_id, content_type, file_name)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        sql::insert_new_draft_file(
 | 
				
			||||||
 | 
					            backend.pool(),
 | 
				
			||||||
 | 
					            user,
 | 
				
			||||||
 | 
					            File {
 | 
				
			||||||
 | 
					                name: match name {
 | 
				
			||||||
 | 
					                    Some(name) => name,
 | 
				
			||||||
 | 
					                    None => filename,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                draft_id,
 | 
				
			||||||
 | 
					                content_type,
 | 
				
			||||||
 | 
					                data,
 | 
				
			||||||
 | 
					                size: size.unwrap_or_default(),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Err(ApiError::MultipartForm(
 | 
				
			||||||
 | 
					            StatusCode::BAD_REQUEST,
 | 
				
			||||||
 | 
					            "Missing fields in request".to_string(),
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/api/routes/files/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/api/routes/files/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					pub mod files_draft_file_get;
 | 
				
			||||||
 | 
					pub mod files_draft_get;
 | 
				
			||||||
 | 
					pub mod files_draft_post;
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/api/routes/files/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/api/routes/files/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					mod handlers;
 | 
				
			||||||
 | 
					mod models;
 | 
				
			||||||
 | 
					mod sql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use axum::extract::DefaultBodyLimit;
 | 
				
			||||||
 | 
					use utoipa_axum::{router::OpenApiRouter, routes};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::routes::users::permissions::{Permission, PermissionDetail},
 | 
				
			||||||
 | 
					    permission_required,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use handlers::{files_draft_file_get::*, files_draft_get::*, files_draft_post::*};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FILE_SIZE_LIMIT_MB: usize = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// expose the OpenAPI to parent module
 | 
				
			||||||
 | 
					pub fn router() -> OpenApiRouter {
 | 
				
			||||||
 | 
					    OpenApiRouter::new()
 | 
				
			||||||
 | 
					        .routes(routes!(get_specified_draft_file))
 | 
				
			||||||
 | 
					        .routes(routes!(upload_draft_file, get_attached_draft_files))
 | 
				
			||||||
 | 
					        .layer(DefaultBodyLimit::max(FILE_SIZE_LIMIT_MB * 1000 * 1000))
 | 
				
			||||||
 | 
					    // .route_layer(permission_required!(Permission::Write(
 | 
				
			||||||
 | 
					    //     PermissionDetail::Users // TODO adjust permissions
 | 
				
			||||||
 | 
					    // )))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/api/routes/files/models.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/api/routes/files/models.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					use sha2::{Digest, Sha256};
 | 
				
			||||||
 | 
					use tokio_util::bytes::Bytes;
 | 
				
			||||||
 | 
					use ts_rs::TS;
 | 
				
			||||||
 | 
					use utoipa::ToSchema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize, TS, ToSchema)]
 | 
				
			||||||
 | 
					#[ts(export)]
 | 
				
			||||||
 | 
					pub struct AttachedFile {
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub hash: String,
 | 
				
			||||||
 | 
					    pub content_type: String,
 | 
				
			||||||
 | 
					    pub size: i32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct File {
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub draft_id: NaiveDateTime,
 | 
				
			||||||
 | 
					    pub content_type: String,
 | 
				
			||||||
 | 
					    pub data: Bytes,
 | 
				
			||||||
 | 
					    pub size: i32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl File {
 | 
				
			||||||
 | 
					    pub fn hash(&self) -> String {
 | 
				
			||||||
 | 
					        let mut hasher = Sha256::new();
 | 
				
			||||||
 | 
					        hasher.update(&self.data);
 | 
				
			||||||
 | 
					        let hash = hasher.finalize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        format!("{hash:X}")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										84
									
								
								src/api/routes/files/sql.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/api/routes/files/sql.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					use sqlx::PgPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{api::routes::users::models::User, errors::ApiError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::models::{AttachedFile, File};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn insert_new_draft_file(pool: &PgPool, user: &User, file: File) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    let mut transaction = pool.begin().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let affected_rows = sqlx::query!(
 | 
				
			||||||
 | 
					        r#"INSERT INTO draftfiles
 | 
				
			||||||
 | 
					        ("UserID", "DraftID", "Hash", "ContentType", "Name", "Data", "Size")
 | 
				
			||||||
 | 
					        VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT DO NOTHING"#,
 | 
				
			||||||
 | 
					        user.user_id,
 | 
				
			||||||
 | 
					        file.draft_id,
 | 
				
			||||||
 | 
					        file.hash(),
 | 
				
			||||||
 | 
					        file.content_type,
 | 
				
			||||||
 | 
					        file.name,
 | 
				
			||||||
 | 
					        &file.data.to_vec(),
 | 
				
			||||||
 | 
					        file.size
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .execute(&mut *transaction)
 | 
				
			||||||
 | 
					    .await?
 | 
				
			||||||
 | 
					    .rows_affected();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // commit transaction
 | 
				
			||||||
 | 
					    transaction.commit().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn get_draft_attached_files(
 | 
				
			||||||
 | 
					    pool: &PgPool,
 | 
				
			||||||
 | 
					    user: &User,
 | 
				
			||||||
 | 
					    draft_id: NaiveDateTime,
 | 
				
			||||||
 | 
					) -> Result<Vec<AttachedFile>, ApiError> {
 | 
				
			||||||
 | 
					    Ok(sqlx::query_as!(
 | 
				
			||||||
 | 
					        AttachedFile,
 | 
				
			||||||
 | 
					        r#"SELECT 
 | 
				
			||||||
 | 
					            "Hash" as hash,
 | 
				
			||||||
 | 
					            "ContentType" as content_type,
 | 
				
			||||||
 | 
					            "Name" as name,
 | 
				
			||||||
 | 
					            "Size" as size
 | 
				
			||||||
 | 
					        FROM 
 | 
				
			||||||
 | 
					            "draftfiles"
 | 
				
			||||||
 | 
					        WHERE
 | 
				
			||||||
 | 
					            "UserID" = $1 AND "DraftID" = $2
 | 
				
			||||||
 | 
					        ORDER BY "Name" ASC"#,
 | 
				
			||||||
 | 
					        user.user_id,
 | 
				
			||||||
 | 
					        draft_id
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .fetch_all(pool)
 | 
				
			||||||
 | 
					    .await?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn get_specified_draft_file(
 | 
				
			||||||
 | 
					    pool: &PgPool,
 | 
				
			||||||
 | 
					    user: &User,
 | 
				
			||||||
 | 
					    draft_id: NaiveDateTime,
 | 
				
			||||||
 | 
					    hash: String,
 | 
				
			||||||
 | 
					) -> Result<Option<File>, ApiError> {
 | 
				
			||||||
 | 
					    let file = sqlx::query_as!(
 | 
				
			||||||
 | 
					        File,
 | 
				
			||||||
 | 
					        r#"SELECT 
 | 
				
			||||||
 | 
					            "ContentType" as content_type,
 | 
				
			||||||
 | 
					            "Name" as name,
 | 
				
			||||||
 | 
					            "Data" as data,
 | 
				
			||||||
 | 
					            "DraftID" as draft_id,
 | 
				
			||||||
 | 
					            "Size" as size
 | 
				
			||||||
 | 
					        FROM 
 | 
				
			||||||
 | 
					            "draftfiles"
 | 
				
			||||||
 | 
					        WHERE
 | 
				
			||||||
 | 
					            "UserID" = $1 AND "DraftID" = $2 AND "Hash" = $3
 | 
				
			||||||
 | 
					        ORDER BY "Name" ASC"#,
 | 
				
			||||||
 | 
					        user.user_id,
 | 
				
			||||||
 | 
					        draft_id,
 | 
				
			||||||
 | 
					        hash
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .fetch_optional(pool)
 | 
				
			||||||
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(file)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
pub mod api_keys;
 | 
					pub mod api_keys;
 | 
				
			||||||
pub mod auth;
 | 
					pub mod auth;
 | 
				
			||||||
 | 
					mod files;
 | 
				
			||||||
mod signature;
 | 
					mod signature;
 | 
				
			||||||
pub mod users;
 | 
					pub mod users;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,6 +45,7 @@ pub fn create_routes(session: AuthBackendType) -> Router {
 | 
				
			|||||||
        .nest(API_BASE, users::router())
 | 
					        .nest(API_BASE, users::router())
 | 
				
			||||||
        .nest(API_BASE, api_keys::router())
 | 
					        .nest(API_BASE, api_keys::router())
 | 
				
			||||||
        .nest(API_BASE, signature::router())
 | 
					        .nest(API_BASE, signature::router())
 | 
				
			||||||
 | 
					        .nest(API_BASE, files::router())
 | 
				
			||||||
        //         .nest(
 | 
					        //         .nest(
 | 
				
			||||||
        //             "/api/order",
 | 
					        //             "/api/order",
 | 
				
			||||||
        //             // order::router().route_layer(crate::login_required!(AuthenticationBackend<ApiKey>)),
 | 
					        //             // order::router().route_layer(crate::login_required!(AuthenticationBackend<ApiKey>)),
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								src/api/routes/signature/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/api/routes/signature/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					pub mod signature_key_get;
 | 
				
			||||||
 | 
					pub mod signature_key_post;
 | 
				
			||||||
 | 
					pub mod signature_key_put;
 | 
				
			||||||
 | 
					pub mod signature_sign_post;
 | 
				
			||||||
 | 
					pub mod signature_verify_get;
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/api/routes/signature/handlers/signature_key_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/api/routes/signature/handlers/signature_key_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					use super::super::{models::SigningKeyStatus, sql};
 | 
				
			||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{description::SIGNATURE_TAG, routes::AuthBackendType},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/signature/key",
 | 
				
			||||||
 | 
					    summary = "Check for signing key",
 | 
				
			||||||
 | 
					    description = "Check if the logged in user has configured a signing key.",
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = SigningKeyStatus, description = "Signature Status"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:signature",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = SIGNATURE_TAG)]
 | 
				
			||||||
 | 
					pub async fn check_signing_key(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					) -> Result<Json<SigningKeyStatus>, ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let private_key = sql::get_private_key(backend.pool(), &user.user_id).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Json(SigningKeyStatus {
 | 
				
			||||||
 | 
					        configured: private_key.key.is_some(),
 | 
				
			||||||
 | 
					        verified: None,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								src/api/routes/signature/handlers/signature_key_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/api/routes/signature/handlers/signature_key_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					use super::super::sql;
 | 
				
			||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					use chrono::Utc;
 | 
				
			||||||
 | 
					use minisign::KeyPair;
 | 
				
			||||||
 | 
					use serde::Deserialize;
 | 
				
			||||||
 | 
					use ts_rs::TS;
 | 
				
			||||||
 | 
					use utoipa::ToSchema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{description::SIGNATURE_TAG, routes::AuthBackendType},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Deserialize, TS, ToSchema)]
 | 
				
			||||||
 | 
					#[ts(export)]
 | 
				
			||||||
 | 
					pub struct CreateSigningKeyRequest {
 | 
				
			||||||
 | 
					    secret: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/signature/key",
 | 
				
			||||||
 | 
					    summary = "Create signing key",
 | 
				
			||||||
 | 
					    description = "Create signing key for the currently logged in user.",
 | 
				
			||||||
 | 
					    request_body(content = CreateSigningKeyRequest, description = "Signing Key Creation Request", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "Successfully created signing key"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:signature",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = SIGNATURE_TAG)]
 | 
				
			||||||
 | 
					pub async fn create_signing_key(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(key_request): Json<CreateSigningKeyRequest>,
 | 
				
			||||||
 | 
					) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create Key Pair
 | 
				
			||||||
 | 
					    let KeyPair { pk, sk } = KeyPair::generate_encrypted_keypair(Some(key_request.secret))?;
 | 
				
			||||||
 | 
					    let public_key = pk.to_box()?.to_string();
 | 
				
			||||||
 | 
					    let private_key = sk
 | 
				
			||||||
 | 
					        .to_box(Some(&format!(
 | 
				
			||||||
 | 
					            "{} {} - {}",
 | 
				
			||||||
 | 
					            user.name,
 | 
				
			||||||
 | 
					            user.surname,
 | 
				
			||||||
 | 
					            Utc::now()
 | 
				
			||||||
 | 
					        )))? // Optional comment about the key
 | 
				
			||||||
 | 
					        .to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // store keys in database
 | 
				
			||||||
 | 
					    sql::update_private_key(backend.pool(), &user.user_id, private_key).await?;
 | 
				
			||||||
 | 
					    sql::store_public_key(backend.pool(), &user.user_id, public_key).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/api/routes/signature/handlers/signature_key_put.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/api/routes/signature/handlers/signature_key_put.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					use super::super::models::{SigningKeyRequest, SigningKeyStatus};
 | 
				
			||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{description::SIGNATURE_TAG, routes::AuthBackendType},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    put,
 | 
				
			||||||
 | 
					    path = "/signature/key",
 | 
				
			||||||
 | 
					    summary = "Verify signing key",
 | 
				
			||||||
 | 
					    description = "Verify signing key for the currently logged in user.",
 | 
				
			||||||
 | 
					    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = SigningKeyStatus, description = "Signature Status"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:signature",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = SIGNATURE_TAG)]
 | 
				
			||||||
 | 
					pub async fn verify_signing_key(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(key_request): Json<SigningKeyRequest>,
 | 
				
			||||||
 | 
					) -> Result<Json<SigningKeyStatus>, ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get private key
 | 
				
			||||||
 | 
					    let status = match key_request
 | 
				
			||||||
 | 
					        .to_private_key(backend.pool(), &user.user_id, backend.private_key_cache())
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Some(_key) => SigningKeyStatus {
 | 
				
			||||||
 | 
					            configured: true,
 | 
				
			||||||
 | 
					            verified: Some(true),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None => SigningKeyStatus {
 | 
				
			||||||
 | 
					            configured: false,
 | 
				
			||||||
 | 
					            verified: None,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Json(status))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										87
									
								
								src/api/routes/signature/handlers/signature_sign_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/api/routes/signature/handlers/signature_sign_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					use std::io::Cursor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::super::{
 | 
				
			||||||
 | 
					    models::{SignatureTest, SigningKeyRequest, VerificationRequest},
 | 
				
			||||||
 | 
					    sql,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{description::SIGNATURE_TAG, routes::AuthBackendType},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/signature/sign",
 | 
				
			||||||
 | 
					    summary = "Sign something with your signing key",
 | 
				
			||||||
 | 
					    description = "Sign something as the currently logged in user.",
 | 
				
			||||||
 | 
					    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "Successfully signed"),
 | 
				
			||||||
 | 
					        (status = 401, description = "Unauthorized"),
 | 
				
			||||||
 | 
					        (status = 500, description = "Internal Server Error")
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:signature",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = SIGNATURE_TAG)]
 | 
				
			||||||
 | 
					pub async fn sign(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(key_request): Json<SigningKeyRequest>,
 | 
				
			||||||
 | 
					) -> Result<Json<VerificationRequest>, ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					    let user = auth_session
 | 
				
			||||||
 | 
					        .is_authenticated()
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get private key
 | 
				
			||||||
 | 
					    let private_key = key_request
 | 
				
			||||||
 | 
					        .to_private_key(backend.pool(), &user.user_id, backend.private_key_cache())
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .ok_or(ApiError::AccessDenied)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get current public key
 | 
				
			||||||
 | 
					    let public_key_id = sql::get_current_public_key(backend.pool(), &user.user_id)
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .ok_or(ApiError::InternalError(
 | 
				
			||||||
 | 
					            "No corresponding public key found".to_string(),
 | 
				
			||||||
 | 
					        ))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let data = SignatureTest {
 | 
				
			||||||
 | 
					        value1: "blfjdljfa".to_string(),
 | 
				
			||||||
 | 
					        value2: false,
 | 
				
			||||||
 | 
					        value3: vec!["bladfa".to_string(), "noch bla".to_string()],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let data_reader = Cursor::new(data.calculate_hash().to_be_bytes());
 | 
				
			||||||
 | 
					    let signature_box = minisign::sign(
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        &private_key,
 | 
				
			||||||
 | 
					        data_reader,
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(&serde_json::to_string(&public_key_id)?),
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We have a signature! Let's inspect it a little bit.
 | 
				
			||||||
 | 
					    println!(
 | 
				
			||||||
 | 
					        "Untrusted comment: [{}]",
 | 
				
			||||||
 | 
					        signature_box.untrusted_comment().unwrap()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    println!(
 | 
				
			||||||
 | 
					        "Trusted comment: [{}]",
 | 
				
			||||||
 | 
					        signature_box.trusted_comment().unwrap()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // store signature
 | 
				
			||||||
 | 
					    // Converting the signature box to a string in order to save it is easy.
 | 
				
			||||||
 | 
					    let signature = signature_box.into_string();
 | 
				
			||||||
 | 
					    println!("BLA");
 | 
				
			||||||
 | 
					    println!("{signature}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Json(VerificationRequest {
 | 
				
			||||||
 | 
					        signature,
 | 
				
			||||||
 | 
					        signed_from: "".to_string(),
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										66
									
								
								src/api/routes/signature/handlers/signature_verify_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/api/routes/signature/handlers/signature_verify_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					use std::io::Cursor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::super::{
 | 
				
			||||||
 | 
					    models::{SignatureTest, SigningKeyRequest, VerificationRequest},
 | 
				
			||||||
 | 
					    sql,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use axum::{debug_handler, Json};
 | 
				
			||||||
 | 
					use minisign::{PublicKeyBox, SignatureBox};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        description::SIGNATURE_TAG,
 | 
				
			||||||
 | 
					        routes::{signature::models::PublicKeyID, AuthBackendType},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/signature/verify",
 | 
				
			||||||
 | 
					    summary = "Verify a signature",
 | 
				
			||||||
 | 
					    description = "Verify the signature.",
 | 
				
			||||||
 | 
					    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "Successfully signed"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = SIGNATURE_TAG)]
 | 
				
			||||||
 | 
					pub async fn verify(
 | 
				
			||||||
 | 
					    auth_session: AuthBackendType,
 | 
				
			||||||
 | 
					    Json(verification_request): Json<VerificationRequest>,
 | 
				
			||||||
 | 
					) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    let backend = auth_session.backend();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Now, let's verify the signature.
 | 
				
			||||||
 | 
					    // Assuming we just loaded it into `signature_box_str`, get the box back.
 | 
				
			||||||
 | 
					    let signature_box = SignatureBox::from_string(&verification_request.signature)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get public key id from signature
 | 
				
			||||||
 | 
					    let public_key_id: PublicKeyID = serde_json::from_str(&signature_box.untrusted_comment()?)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Load the public key of the user that signed.
 | 
				
			||||||
 | 
					    let public_key_str = sql::get_public_key_by_id(backend.pool(), public_key_id)
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .key
 | 
				
			||||||
 | 
					        .ok_or(ApiError::InternalError(
 | 
				
			||||||
 | 
					            "No corresponding Public Key was found.".to_string(),
 | 
				
			||||||
 | 
					        ))?;
 | 
				
			||||||
 | 
					    let public_key_box = PublicKeyBox::from_string(&public_key_str)?;
 | 
				
			||||||
 | 
					    let public_key = public_key_box.into_public_key()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // And verify the data.
 | 
				
			||||||
 | 
					    let data = SignatureTest {
 | 
				
			||||||
 | 
					        value1: "blfjdljfa".to_string(),
 | 
				
			||||||
 | 
					        value2: false,
 | 
				
			||||||
 | 
					        value3: vec!["bladfa".to_string(), "noch bla".to_string()],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    let data_reader = Cursor::new(data.calculate_hash().to_be_bytes());
 | 
				
			||||||
 | 
					    let verified = minisign::verify(&public_key, &signature_box, data_reader, true, false, false);
 | 
				
			||||||
 | 
					    match verified {
 | 
				
			||||||
 | 
					        Ok(()) => println!("Success!"),
 | 
				
			||||||
 | 
					        Err(_) => println!("Verification failed"),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,24 +1,17 @@
 | 
				
			|||||||
use std::io::Cursor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use axum::{debug_handler, Json};
 | 
					 | 
				
			||||||
use chrono::Utc;
 | 
					 | 
				
			||||||
use minisign::{KeyPair, PublicKeyBox, SecretKeyBox, SignatureBox};
 | 
					 | 
				
			||||||
use models::{
 | 
					 | 
				
			||||||
    PublicKeyID, SignatureTest, SigningKeyRequest, SigningKeyStatus, VerificationRequest,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use utoipa_axum::{router::OpenApiRouter, routes};
 | 
					use utoipa_axum::{router::OpenApiRouter, routes};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    api::{
 | 
					    api::routes::users::permissions::{Permission, PermissionDetail},
 | 
				
			||||||
        description::SIGNATURE_TAG,
 | 
					 | 
				
			||||||
        routes::users::permissions::{Permission, PermissionDetail},
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    errors::ApiError,
 | 
					 | 
				
			||||||
    permission_required,
 | 
					    permission_required,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::AuthBackendType;
 | 
					use handlers::signature_key_get::*;
 | 
				
			||||||
 | 
					use handlers::signature_key_post::*;
 | 
				
			||||||
 | 
					use handlers::signature_key_put::*;
 | 
				
			||||||
 | 
					use handlers::signature_sign_post::*;
 | 
				
			||||||
 | 
					use handlers::signature_verify_get::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod handlers;
 | 
				
			||||||
pub mod models;
 | 
					pub mod models;
 | 
				
			||||||
pub mod sql;
 | 
					pub mod sql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,238 +31,3 @@ pub fn router() -> OpenApiRouter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    OpenApiRouter::new().merge(write).merge(verify)
 | 
					    OpenApiRouter::new().merge(write).merge(verify)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    get,
 | 
					 | 
				
			||||||
    path = "/signature/key",
 | 
					 | 
				
			||||||
    summary = "Check for signing key",
 | 
					 | 
				
			||||||
    description = "Check if the logged in user has configured a signing key.",
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, body = SigningKeyStatus, description = "Signature Status"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:signature",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = SIGNATURE_TAG)]
 | 
					 | 
				
			||||||
pub async fn check_signing_key(
 | 
					 | 
				
			||||||
    auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
) -> Result<Json<SigningKeyStatus>, ApiError> {
 | 
					 | 
				
			||||||
    let backend = auth_session.backend();
 | 
					 | 
				
			||||||
    let user = auth_session
 | 
					 | 
				
			||||||
        .is_authenticated()
 | 
					 | 
				
			||||||
        .ok_or(ApiError::AccessDenied)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let private_key = sql::get_private_key(backend.pool(), &user.user_id).await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(Json(SigningKeyStatus {
 | 
					 | 
				
			||||||
        configured: private_key.key.is_some(),
 | 
					 | 
				
			||||||
        verified: None,
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    post,
 | 
					 | 
				
			||||||
    path = "/signature/key",
 | 
					 | 
				
			||||||
    summary = "Create signing key",
 | 
					 | 
				
			||||||
    description = "Create signing key for the currently logged in user.",
 | 
					 | 
				
			||||||
    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "Successfully created signing key"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:signature",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = SIGNATURE_TAG)]
 | 
					 | 
				
			||||||
pub async fn create_signing_key(
 | 
					 | 
				
			||||||
    auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
    Json(key_request): Json<SigningKeyRequest>,
 | 
					 | 
				
			||||||
) -> Result<(), ApiError> {
 | 
					 | 
				
			||||||
    let backend = auth_session.backend();
 | 
					 | 
				
			||||||
    let user = auth_session
 | 
					 | 
				
			||||||
        .is_authenticated()
 | 
					 | 
				
			||||||
        .ok_or(ApiError::AccessDenied)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // create Key Pair
 | 
					 | 
				
			||||||
    let KeyPair { pk, sk } = KeyPair::generate_encrypted_keypair(Some(key_request.secret))?;
 | 
					 | 
				
			||||||
    let public_key = pk.to_box()?.to_string();
 | 
					 | 
				
			||||||
    let private_key = sk
 | 
					 | 
				
			||||||
        .to_box(Some(&format!(
 | 
					 | 
				
			||||||
            "{} {} - {}",
 | 
					 | 
				
			||||||
            user.name,
 | 
					 | 
				
			||||||
            user.surname,
 | 
					 | 
				
			||||||
            Utc::now()
 | 
					 | 
				
			||||||
        )))? // Optional comment about the key
 | 
					 | 
				
			||||||
        .to_string();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // store keys in database
 | 
					 | 
				
			||||||
    sql::update_private_key(backend.pool(), &user.user_id, private_key).await?;
 | 
					 | 
				
			||||||
    sql::store_public_key(backend.pool(), &user.user_id, public_key).await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    put,
 | 
					 | 
				
			||||||
    path = "/signature/key",
 | 
					 | 
				
			||||||
    summary = "Verify signing key",
 | 
					 | 
				
			||||||
    description = "Verify signing key for the currently logged in user.",
 | 
					 | 
				
			||||||
    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, body = SigningKeyStatus, description = "Signature Status"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:signature",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = SIGNATURE_TAG)]
 | 
					 | 
				
			||||||
pub async fn verify_signing_key(
 | 
					 | 
				
			||||||
    auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
    Json(key_request): Json<SigningKeyRequest>,
 | 
					 | 
				
			||||||
) -> Result<Json<SigningKeyStatus>, ApiError> {
 | 
					 | 
				
			||||||
    let backend = auth_session.backend();
 | 
					 | 
				
			||||||
    let user = auth_session
 | 
					 | 
				
			||||||
        .is_authenticated()
 | 
					 | 
				
			||||||
        .ok_or(ApiError::AccessDenied)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // get private key
 | 
					 | 
				
			||||||
    let status = match key_request
 | 
					 | 
				
			||||||
        .to_private_key(backend.pool(), &user.user_id)
 | 
					 | 
				
			||||||
        .await?
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Some(_key) => SigningKeyStatus {
 | 
					 | 
				
			||||||
            configured: true,
 | 
					 | 
				
			||||||
            verified: Some(true),
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        None => SigningKeyStatus {
 | 
					 | 
				
			||||||
            configured: false,
 | 
					 | 
				
			||||||
            verified: None,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(Json(status))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    post,
 | 
					 | 
				
			||||||
    path = "/signature/sign",
 | 
					 | 
				
			||||||
    summary = "Sign something with your signing key",
 | 
					 | 
				
			||||||
    description = "Sign something as the currently logged in user.",
 | 
					 | 
				
			||||||
    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "Successfully signed"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:signature",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = SIGNATURE_TAG)]
 | 
					 | 
				
			||||||
pub async fn sign(
 | 
					 | 
				
			||||||
    auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
    Json(key_request): Json<SigningKeyRequest>,
 | 
					 | 
				
			||||||
) -> Result<Json<VerificationRequest>, ApiError> {
 | 
					 | 
				
			||||||
    let backend = auth_session.backend();
 | 
					 | 
				
			||||||
    let user = auth_session
 | 
					 | 
				
			||||||
        .is_authenticated()
 | 
					 | 
				
			||||||
        .ok_or(ApiError::AccessDenied)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // get private key
 | 
					 | 
				
			||||||
    let private_key = key_request
 | 
					 | 
				
			||||||
        .to_private_key(backend.pool(), &user.user_id)
 | 
					 | 
				
			||||||
        .await?
 | 
					 | 
				
			||||||
        .ok_or(ApiError::AccessDenied)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // get current public key
 | 
					 | 
				
			||||||
    let public_key_id = sql::get_current_public_key(backend.pool(), &user.user_id)
 | 
					 | 
				
			||||||
        .await?
 | 
					 | 
				
			||||||
        .ok_or(ApiError::InternalError(
 | 
					 | 
				
			||||||
            "No corresponding public key found".to_string(),
 | 
					 | 
				
			||||||
        ))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data = SignatureTest {
 | 
					 | 
				
			||||||
        value1: "blfjdljfa".to_string(),
 | 
					 | 
				
			||||||
        value2: false,
 | 
					 | 
				
			||||||
        value3: vec!["bladfa".to_string(), "noch bla".to_string()],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let data_reader = Cursor::new(data.calculate_hash().to_be_bytes());
 | 
					 | 
				
			||||||
    let signature_box = minisign::sign(
 | 
					 | 
				
			||||||
        None,
 | 
					 | 
				
			||||||
        &private_key,
 | 
					 | 
				
			||||||
        data_reader,
 | 
					 | 
				
			||||||
        None,
 | 
					 | 
				
			||||||
        Some(&serde_json::to_string(&public_key_id)?),
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // We have a signature! Let's inspect it a little bit.
 | 
					 | 
				
			||||||
    println!(
 | 
					 | 
				
			||||||
        "Untrusted comment: [{}]",
 | 
					 | 
				
			||||||
        signature_box.untrusted_comment().unwrap()
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    println!(
 | 
					 | 
				
			||||||
        "Trusted comment: [{}]",
 | 
					 | 
				
			||||||
        signature_box.trusted_comment().unwrap()
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // store signature
 | 
					 | 
				
			||||||
    // Converting the signature box to a string in order to save it is easy.
 | 
					 | 
				
			||||||
    let signature = signature_box.into_string();
 | 
					 | 
				
			||||||
    println!("BLA");
 | 
					 | 
				
			||||||
    println!("{signature}");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(Json(VerificationRequest {
 | 
					 | 
				
			||||||
        signature,
 | 
					 | 
				
			||||||
        signed_from: "".to_string(),
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    get,
 | 
					 | 
				
			||||||
    path = "/signature/verify",
 | 
					 | 
				
			||||||
    summary = "Verify a signature",
 | 
					 | 
				
			||||||
    description = "Verify the signature.",
 | 
					 | 
				
			||||||
    request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "Successfully signed"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = SIGNATURE_TAG)]
 | 
					 | 
				
			||||||
pub async fn verify(
 | 
					 | 
				
			||||||
    auth_session: AuthBackendType,
 | 
					 | 
				
			||||||
    Json(verification_request): Json<VerificationRequest>,
 | 
					 | 
				
			||||||
) -> Result<(), ApiError> {
 | 
					 | 
				
			||||||
    let backend = auth_session.backend();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Now, let's verify the signature.
 | 
					 | 
				
			||||||
    // Assuming we just loaded it into `signature_box_str`, get the box back.
 | 
					 | 
				
			||||||
    let signature_box = SignatureBox::from_string(&verification_request.signature)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // get public key id from signature
 | 
					 | 
				
			||||||
    let public_key_id: PublicKeyID = serde_json::from_str(&signature_box.untrusted_comment()?)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Load the public key of the user that signed.
 | 
					 | 
				
			||||||
    let public_key_str = sql::get_public_key_by_id(backend.pool(), public_key_id)
 | 
					 | 
				
			||||||
        .await?
 | 
					 | 
				
			||||||
        .key
 | 
					 | 
				
			||||||
        .ok_or(ApiError::InternalError(
 | 
					 | 
				
			||||||
            "No corresponding Public Key was found.".to_string(),
 | 
					 | 
				
			||||||
        ))?;
 | 
					 | 
				
			||||||
    let public_key_box = PublicKeyBox::from_string(&public_key_str)?;
 | 
					 | 
				
			||||||
    let public_key = public_key_box.into_public_key()?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // And verify the data.
 | 
					 | 
				
			||||||
    let data = SignatureTest {
 | 
					 | 
				
			||||||
        value1: "blfjdljfa".to_string(),
 | 
					 | 
				
			||||||
        value2: false,
 | 
					 | 
				
			||||||
        value3: vec!["bladfa".to_string(), "noch bla".to_string()],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    let data_reader = Cursor::new(data.calculate_hash().to_be_bytes());
 | 
					 | 
				
			||||||
    let verified = minisign::verify(&public_key, &signature_box, data_reader, true, false, false);
 | 
					 | 
				
			||||||
    match verified {
 | 
					 | 
				
			||||||
        Ok(()) => println!("Success!"),
 | 
					 | 
				
			||||||
        Err(_) => println!("Verification failed"),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    fmt::Display,
 | 
					 | 
				
			||||||
    hash::{DefaultHasher, Hash, Hasher},
 | 
					    hash::{DefaultHasher, Hash, Hasher},
 | 
				
			||||||
 | 
					    time::Duration,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono::NaiveDateTime;
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
@ -10,7 +10,10 @@ use sqlx::PgPool;
 | 
				
			|||||||
use ts_rs::TS;
 | 
					use ts_rs::TS;
 | 
				
			||||||
use utoipa::ToSchema;
 | 
					use utoipa::ToSchema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{api::routes::signature::sql, errors::ApiError};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{backend::private_key_cache::PrivateKeyCache, routes::signature::sql},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
 | 
				
			||||||
#[ts(export)]
 | 
					#[ts(export)]
 | 
				
			||||||
@ -32,7 +35,10 @@ pub struct PublicKeyID {
 | 
				
			|||||||
#[derive(Debug, Clone, Deserialize, TS, ToSchema)]
 | 
					#[derive(Debug, Clone, Deserialize, TS, ToSchema)]
 | 
				
			||||||
#[ts(export)]
 | 
					#[ts(export)]
 | 
				
			||||||
pub struct SigningKeyRequest {
 | 
					pub struct SigningKeyRequest {
 | 
				
			||||||
    pub secret: String,
 | 
					    // if no secret is given, the key will be searched in the cache
 | 
				
			||||||
 | 
					    pub secret: Option<String>,
 | 
				
			||||||
 | 
					    // if ttl is set, the key is cached for ttl in seconds
 | 
				
			||||||
 | 
					    pub ttl: Option<u32>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SigningKeyRequest {
 | 
					impl SigningKeyRequest {
 | 
				
			||||||
@ -40,19 +46,41 @@ impl SigningKeyRequest {
 | 
				
			|||||||
        self,
 | 
					        self,
 | 
				
			||||||
        pool: &PgPool,
 | 
					        pool: &PgPool,
 | 
				
			||||||
        user_id: &str,
 | 
					        user_id: &str,
 | 
				
			||||||
 | 
					        private_key_cache: PrivateKeyCache,
 | 
				
			||||||
    ) -> Result<Option<SecretKey>, ApiError> {
 | 
					    ) -> Result<Option<SecretKey>, ApiError> {
 | 
				
			||||||
 | 
					        // get private key from database and decrypt it or get it from cache
 | 
				
			||||||
 | 
					        // this depends on the request containing a secret
 | 
				
			||||||
 | 
					        // if a secret and a TTL is given the decrypted private key is cached
 | 
				
			||||||
 | 
					        let private_key = match self.secret {
 | 
				
			||||||
 | 
					            Some(secret) => {
 | 
				
			||||||
                // try to get private key from database
 | 
					                // try to get private key from database
 | 
				
			||||||
        let private_key = match sql::get_private_key(pool, user_id).await?.key {
 | 
					                let encrypted_private_key = match sql::get_private_key(pool, user_id).await?.key {
 | 
				
			||||||
                    Some(key) => key,
 | 
					                    Some(key) => key,
 | 
				
			||||||
                    None => return Ok(None),
 | 
					                    None => return Ok(None),
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // create MinSign Box from key string
 | 
					                // create MinSign Box from key string
 | 
				
			||||||
        let private_key_box = SecretKeyBox::from_string(&private_key)?;
 | 
					                let private_key_box = SecretKeyBox::from_string(&encrypted_private_key)?;
 | 
				
			||||||
                // and the box can be opened using the password to reveal the original secret key:
 | 
					                // and the box can be opened using the password to reveal the original secret key:
 | 
				
			||||||
        let secret_key = private_key_box.into_secret_key(Some(self.secret))?;
 | 
					                let private_key = private_key_box.into_secret_key(Some(secret))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Some(secret_key))
 | 
					                // cache key if TTL is set
 | 
				
			||||||
 | 
					                if let Some(ttl) = self.ttl {
 | 
				
			||||||
 | 
					                    let duration = Duration::from_secs(ttl.into());
 | 
				
			||||||
 | 
					                    private_key_cache
 | 
				
			||||||
 | 
					                        .add_key(user_id, &private_key, duration)
 | 
				
			||||||
 | 
					                        .await;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                private_key
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            None => match private_key_cache.get_key(user_id).await {
 | 
				
			||||||
 | 
					                Some(key) => key.secret_key(),
 | 
				
			||||||
 | 
					                None => return Err(ApiError::InvalidCredentials),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Some(private_key))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								src/api/routes/users/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/api/routes/users/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					pub mod users_available_ad_get;
 | 
				
			||||||
 | 
					pub mod users_get;
 | 
				
			||||||
 | 
					pub mod users_post;
 | 
				
			||||||
 | 
					pub mod users_put;
 | 
				
			||||||
							
								
								
									
										53
									
								
								src/api/routes/users/handlers/users_available_ad_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/api/routes/users/handlers/users_available_ad_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Extension, Json};
 | 
				
			||||||
 | 
					use axum_jwt_login::UserPermissions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        backend::{
 | 
				
			||||||
 | 
					            ldap::{ActiveDirectoryUser, LDAPBackend},
 | 
				
			||||||
 | 
					            ApiBackend,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        description::USERS_TAG,
 | 
				
			||||||
 | 
					        routes::{auth::models::Credentials, users::sql},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/users/available_ad_users",
 | 
				
			||||||
 | 
					    summary = "Get Active Directory Users",
 | 
				
			||||||
 | 
					    description = "Get all Available Users from the Active Directory that are not already registered with this API.",
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = Vec<ActiveDirectoryUser>, description = "List of AD users"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:users",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = USERS_TAG)]
 | 
				
			||||||
 | 
					pub async fn get_ad_users(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					    Json(credentials): Json<Option<Credentials>>,
 | 
				
			||||||
 | 
					) -> Result<Json<Vec<ActiveDirectoryUser>>, ApiError> {
 | 
				
			||||||
 | 
					    let api_user_ids: Vec<String> = sql::get_users(backend.pool(), None, None)
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .map(|user| user.id())
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					    let mut ldap = LDAPBackend::from_config(backend.config()).await?;
 | 
				
			||||||
 | 
					    // bind to AD user if credentials are given
 | 
				
			||||||
 | 
					    if let Some(credentials) = credentials {
 | 
				
			||||||
 | 
					        ldap.ad_bind(&credentials.id, &credentials.password).await?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let ad_users = ldap
 | 
				
			||||||
 | 
					        .get_ad_user_list()
 | 
				
			||||||
 | 
					        .await?
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .filter(|entry| !api_user_ids.contains(&entry.id))
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					    // disconnect from AD server
 | 
				
			||||||
 | 
					    ldap.unbind().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Json(ad_users))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/api/routes/users/handlers/users_get.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/api/routes/users/handlers/users_get.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Extension, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::super::sql;
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{backend::ApiBackend, description::USERS_TAG, routes::users::models::User},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    get,
 | 
				
			||||||
 | 
					    path = "/users",
 | 
				
			||||||
 | 
					    summary = "Get all Users",
 | 
				
			||||||
 | 
					    description = "Get a list of all users.",
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, body = Vec<User>, description = "List of users"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["read:users",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = USERS_TAG)]
 | 
				
			||||||
 | 
					pub async fn get_users(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					) -> Result<Json<Vec<User>>, ApiError> {
 | 
				
			||||||
 | 
					    Ok(Json(sql::get_users(backend.pool(), None, None).await?))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/api/routes/users/handlers/users_post.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/api/routes/users/handlers/users_post.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Extension, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::super::sql;
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{backend::ApiBackend, description::USERS_TAG, routes::users::models::User},
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					    utils::create_random,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    post,
 | 
				
			||||||
 | 
					    path = "/users",
 | 
				
			||||||
 | 
					    summary = "Create a new User",
 | 
				
			||||||
 | 
					    description = "Creates a new user with the given information ",
 | 
				
			||||||
 | 
					    request_body(content = User, description = "User details", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "User successfully created (Assigned Password in body)", body = String),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:users",]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = USERS_TAG)]
 | 
				
			||||||
 | 
					pub async fn create_user(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					    Json(user): Json<User>,
 | 
				
			||||||
 | 
					) -> Result<String, ApiError> {
 | 
				
			||||||
 | 
					    // create password if not Active Directory user
 | 
				
			||||||
 | 
					    let (password, hash) = match user.active_directory_auth {
 | 
				
			||||||
 | 
					        true => (String::new(), None),
 | 
				
			||||||
 | 
					        false => {
 | 
				
			||||||
 | 
					            let salt = create_random(20);
 | 
				
			||||||
 | 
					            let argon_config = argon2::Config::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let password = create_random(10);
 | 
				
			||||||
 | 
					            let password_hash =
 | 
				
			||||||
 | 
					                argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &argon_config)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            (password, Some(password_hash))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    // create user
 | 
				
			||||||
 | 
					    sql::create_new_user(backend.pool(), &user, hash).await?;
 | 
				
			||||||
 | 
					    // send created password back to frontend
 | 
				
			||||||
 | 
					    Ok(password)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/api/routes/users/handlers/users_put.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/api/routes/users/handlers/users_put.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					use axum::{debug_handler, Extension, Json};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    api::{
 | 
				
			||||||
 | 
					        backend::ApiBackend,
 | 
				
			||||||
 | 
					        description::USERS_TAG,
 | 
				
			||||||
 | 
					        routes::users::{models::User, sql},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    errors::ApiError,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[debug_handler]
 | 
				
			||||||
 | 
					#[utoipa::path(
 | 
				
			||||||
 | 
					    put,
 | 
				
			||||||
 | 
					    path = "/users",
 | 
				
			||||||
 | 
					    summary = "Change User details",
 | 
				
			||||||
 | 
					    description = "Update user information / permissions / groups ",
 | 
				
			||||||
 | 
					    request_body(content = User, description = "User details", content_type = "application/json"),
 | 
				
			||||||
 | 
					    responses(
 | 
				
			||||||
 | 
					        (status = OK, description = "User successfully updated"),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    security(
 | 
				
			||||||
 | 
					        ("user_auth" = ["write:users"]),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    tag = USERS_TAG)]
 | 
				
			||||||
 | 
					pub async fn update_user(
 | 
				
			||||||
 | 
					    Extension(backend): Extension<ApiBackend>,
 | 
				
			||||||
 | 
					    Json(user): Json<User>,
 | 
				
			||||||
 | 
					) -> Result<(), ApiError> {
 | 
				
			||||||
 | 
					    sql::update_user(backend.pool(), &user).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,23 +1,14 @@
 | 
				
			|||||||
use axum::{debug_handler, Extension, Json};
 | 
					 | 
				
			||||||
use axum_jwt_login::UserPermissions;
 | 
					 | 
				
			||||||
use models::User;
 | 
					 | 
				
			||||||
use permissions::{Permission, PermissionDetail};
 | 
					use permissions::{Permission, PermissionDetail};
 | 
				
			||||||
use utoipa_axum::{router::OpenApiRouter, routes};
 | 
					use utoipa_axum::{router::OpenApiRouter, routes};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::permission_required;
 | 
				
			||||||
    api::{
 | 
					 | 
				
			||||||
        backend::{
 | 
					 | 
				
			||||||
            ldap::{ActiveDirectoryUser, LDAPBackend},
 | 
					 | 
				
			||||||
            ApiBackend,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        description::USERS_TAG,
 | 
					 | 
				
			||||||
        routes::auth::models::Credentials,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    errors::ApiError,
 | 
					 | 
				
			||||||
    permission_required,
 | 
					 | 
				
			||||||
    utils::create_random,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use handlers::users_available_ad_get::*;
 | 
				
			||||||
 | 
					use handlers::users_get::*;
 | 
				
			||||||
 | 
					use handlers::users_post::*;
 | 
				
			||||||
 | 
					use handlers::users_put::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod handlers;
 | 
				
			||||||
pub mod models;
 | 
					pub mod models;
 | 
				
			||||||
pub mod permissions;
 | 
					pub mod permissions;
 | 
				
			||||||
pub mod sql;
 | 
					pub mod sql;
 | 
				
			||||||
@ -38,122 +29,3 @@ pub fn router() -> OpenApiRouter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    OpenApiRouter::new().merge(read).merge(write)
 | 
					    OpenApiRouter::new().merge(read).merge(write)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    get,
 | 
					 | 
				
			||||||
    path = "/users",
 | 
					 | 
				
			||||||
    summary = "Get all Users",
 | 
					 | 
				
			||||||
    description = "Get a list of all users.",
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, body = Vec<User>, description = "List of users"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["read:users",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = USERS_TAG)]
 | 
					 | 
				
			||||||
pub async fn get_users(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
) -> Result<Json<Vec<User>>, ApiError> {
 | 
					 | 
				
			||||||
    Ok(Json(sql::get_users(backend.pool(), None, None).await?))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    put,
 | 
					 | 
				
			||||||
    path = "/users",
 | 
					 | 
				
			||||||
    summary = "Change User details",
 | 
					 | 
				
			||||||
    description = "Update user information / permissions / groups ",
 | 
					 | 
				
			||||||
    request_body(content = User, description = "User details", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "User successfully updated"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:users"]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = USERS_TAG)]
 | 
					 | 
				
			||||||
pub async fn update_user(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
    Json(user): Json<User>,
 | 
					 | 
				
			||||||
) -> Result<(), ApiError> {
 | 
					 | 
				
			||||||
    sql::update_user(backend.pool(), &user).await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    post,
 | 
					 | 
				
			||||||
    path = "/users",
 | 
					 | 
				
			||||||
    summary = "Create a new User",
 | 
					 | 
				
			||||||
    description = "Creates a new user with the given information ",
 | 
					 | 
				
			||||||
    request_body(content = User, description = "User details", content_type = "application/json"),
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, description = "User successfully created (Assigned Password in body)", body = String),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:users",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = USERS_TAG)]
 | 
					 | 
				
			||||||
pub async fn create_user(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
    Json(user): Json<User>,
 | 
					 | 
				
			||||||
) -> Result<String, ApiError> {
 | 
					 | 
				
			||||||
    // create password if not Active Directory user
 | 
					 | 
				
			||||||
    let (password, hash) = match user.active_directory_auth {
 | 
					 | 
				
			||||||
        true => (String::new(), None),
 | 
					 | 
				
			||||||
        false => {
 | 
					 | 
				
			||||||
            let salt = create_random(20);
 | 
					 | 
				
			||||||
            let argon_config = argon2::Config::default();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let password = create_random(10);
 | 
					 | 
				
			||||||
            let password_hash =
 | 
					 | 
				
			||||||
                argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &argon_config)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            (password, Some(password_hash))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // create user
 | 
					 | 
				
			||||||
    sql::create_new_user(backend.pool(), &user, hash).await?;
 | 
					 | 
				
			||||||
    // send created password back to frontend
 | 
					 | 
				
			||||||
    Ok(password)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					 | 
				
			||||||
#[utoipa::path(
 | 
					 | 
				
			||||||
    get,
 | 
					 | 
				
			||||||
    path = "/users/available_ad_users",
 | 
					 | 
				
			||||||
    summary = "Get Active Directory Users",
 | 
					 | 
				
			||||||
    description = "Get all Available Users from the Active Directory that are not already registered with this API.",
 | 
					 | 
				
			||||||
    responses(
 | 
					 | 
				
			||||||
        (status = OK, body = Vec<ActiveDirectoryUser>, description = "List of AD users"),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    security(
 | 
					 | 
				
			||||||
        ("user_auth" = ["write:users",]),
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    tag = USERS_TAG)]
 | 
					 | 
				
			||||||
pub async fn get_ad_users(
 | 
					 | 
				
			||||||
    Extension(backend): Extension<ApiBackend>,
 | 
					 | 
				
			||||||
    Json(credentials): Json<Option<Credentials>>,
 | 
					 | 
				
			||||||
) -> Result<Json<Vec<ActiveDirectoryUser>>, ApiError> {
 | 
					 | 
				
			||||||
    let api_user_ids: Vec<String> = sql::get_users(backend.pool(), None, None)
 | 
					 | 
				
			||||||
        .await?
 | 
					 | 
				
			||||||
        .iter()
 | 
					 | 
				
			||||||
        .map(|user| user.id())
 | 
					 | 
				
			||||||
        .collect();
 | 
					 | 
				
			||||||
    let mut ldap = LDAPBackend::from_config(backend.config()).await?;
 | 
					 | 
				
			||||||
    // bind to AD user if credentials are given
 | 
					 | 
				
			||||||
    if let Some(credentials) = credentials {
 | 
					 | 
				
			||||||
        ldap.ad_bind(&credentials.id, &credentials.password).await?;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    let ad_users = ldap
 | 
					 | 
				
			||||||
        .get_ad_user_list()
 | 
					 | 
				
			||||||
        .await?
 | 
					 | 
				
			||||||
        .into_iter()
 | 
					 | 
				
			||||||
        .filter(|entry| !api_user_ids.contains(&entry.id))
 | 
					 | 
				
			||||||
        .collect();
 | 
					 | 
				
			||||||
    // disconnect from AD server
 | 
					 | 
				
			||||||
    ldap.unbind().await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(Json(ad_users))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,12 @@
 | 
				
			|||||||
use std::fmt::{self, Display};
 | 
					use std::fmt::{self, Display};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use axum::{
 | 
					use axum::{
 | 
				
			||||||
 | 
					    extract::multipart::MultipartError,
 | 
				
			||||||
    http::StatusCode,
 | 
					    http::StatusCode,
 | 
				
			||||||
    response::{IntoResponse, Response},
 | 
					    response::{IntoResponse, Response},
 | 
				
			||||||
    Json,
 | 
					    Json,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					use chrono::{NaiveDateTime, ParseError, ParseResult};
 | 
				
			||||||
use error_stack::Context;
 | 
					use error_stack::Context;
 | 
				
			||||||
use minisign::PError;
 | 
					use minisign::PError;
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::Serialize;
 | 
				
			||||||
@ -36,6 +38,9 @@ pub enum ApiError {
 | 
				
			|||||||
    InvalidCredentials,
 | 
					    InvalidCredentials,
 | 
				
			||||||
    InternalError(String),
 | 
					    InternalError(String),
 | 
				
			||||||
    AccessDenied,
 | 
					    AccessDenied,
 | 
				
			||||||
 | 
					    MultipartForm(StatusCode, String),
 | 
				
			||||||
 | 
					    InvalidRequest(String),
 | 
				
			||||||
 | 
					    FileNotFound,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ApiError {
 | 
					impl ApiError {
 | 
				
			||||||
@ -44,7 +49,7 @@ impl ApiError {
 | 
				
			|||||||
            Self::InvalidCredentials => (StatusCode::UNAUTHORIZED, "Invalid credentials", None),
 | 
					            Self::InvalidCredentials => (StatusCode::UNAUTHORIZED, "Invalid credentials", None),
 | 
				
			||||||
            Self::SQLQueryError(error) => (
 | 
					            Self::SQLQueryError(error) => (
 | 
				
			||||||
                StatusCode::INTERNAL_SERVER_ERROR,
 | 
					                StatusCode::INTERNAL_SERVER_ERROR,
 | 
				
			||||||
                "Invalid credentials",
 | 
					                "Error executing SQL request",
 | 
				
			||||||
                Some(error),
 | 
					                Some(error),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Self::InternalError(error) => (
 | 
					            Self::InternalError(error) => (
 | 
				
			||||||
@ -52,6 +57,13 @@ impl ApiError {
 | 
				
			|||||||
                "Internal Server Error",
 | 
					                "Internal Server Error",
 | 
				
			||||||
                Some(error),
 | 
					                Some(error),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            Self::InvalidRequest(s) => (
 | 
				
			||||||
 | 
					                StatusCode::BAD_REQUEST,
 | 
				
			||||||
 | 
					                "Request contains invalid or missing data",
 | 
				
			||||||
 | 
					                Some(s),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Self::MultipartForm(c, s) => (*c, "Multipart Error", Some(s)),
 | 
				
			||||||
 | 
					            Self::FileNotFound => (StatusCode::NOT_FOUND, "File not found", None),
 | 
				
			||||||
            Self::AccessDenied => (StatusCode::FORBIDDEN, "Access Denied", None), // ApiError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
 | 
					            Self::AccessDenied => (StatusCode::FORBIDDEN, "Access Denied", None), // ApiError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
 | 
				
			||||||
                                                                                  // ApiError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"),
 | 
					                                                                                  // ApiError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"),
 | 
				
			||||||
                                                                                  // ApiError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"),
 | 
					                                                                                  // ApiError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"),
 | 
				
			||||||
@ -123,6 +135,12 @@ impl From<serde_json::Error> for ApiError {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<MultipartError> for ApiError {
 | 
				
			||||||
 | 
					    fn from(value: MultipartError) -> Self {
 | 
				
			||||||
 | 
					        Self::MultipartForm(value.status(), value.body_text())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Display for ApiError {
 | 
					impl Display for ApiError {
 | 
				
			||||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
        let error_info = self.as_error_info().1;
 | 
					        let error_info = self.as_error_info().1;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/migrations/07_draft_files.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/migrations/07_draft_files.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					CREATE TABLE IF NOT EXISTS public.draftfiles
 | 
				
			||||||
 | 
					(
 | 
				
			||||||
 | 
					    "DraftID" timestamp without time zone NOT NULL,
 | 
				
			||||||
 | 
					    "UserID" character varying(10) COLLATE pg_catalog."default" NOT NULL,
 | 
				
			||||||
 | 
					    "ContentType" character varying(255) COLLATE pg_catalog."default" NOT NULL,
 | 
				
			||||||
 | 
					    "Name" character varying COLLATE pg_catalog."default" NOT NULL,
 | 
				
			||||||
 | 
					    "Hash" character varying COLLATE pg_catalog."default" NOT NULL,
 | 
				
			||||||
 | 
					    "Data" bytea NOT NULL,
 | 
				
			||||||
 | 
					    "Size" integer NOT NULL,
 | 
				
			||||||
 | 
					    CONSTRAINT draftfiles_pkey PRIMARY KEY ("DraftID", "UserID", "Hash"),
 | 
				
			||||||
 | 
					    CONSTRAINT "UserID" FOREIGN KEY ("UserID")
 | 
				
			||||||
 | 
					        REFERENCES public.users ("UserID") MATCH SIMPLE
 | 
				
			||||||
 | 
					        ON UPDATE NO ACTION
 | 
				
			||||||
 | 
					        ON DELETE NO ACTION
 | 
				
			||||||
 | 
					        NOT VALID
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TABLESPACE pg_default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE IF EXISTS public.draftfiles
 | 
				
			||||||
 | 
					    OWNER to postgres;
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user