diff --git a/docs/openapi.json b/docs/openapi.json index 64691fd9..2394db39 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1 +1 @@ -{"openapi":"3.0.0","info":{"title":"Twake on Matrix APIs documentation","version":"0.0.1","description":"This is The documentation of all available APIs of this repository"},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"MatrixError":{"type":"object","properties":{"errcode":{"type":"string","description":"A Matrix error code"},"error":{"type":"string","description":"A human-readable error message"}},"required":["error"]},"ActiveContacts":{"type":"object","description":"the list of active contacts","properties":{"contacts":{"type":"string","description":"active contacts"}}},"MutualRooms":{"type":"array","items":{"type":"object","properties":{"roomId":{"type":"string","description":"the room id"},"name":{"type":"string","description":"the room name"},"topic":{"type":"string","description":"the room topic"},"room_type":{"type":"string","description":"the room type"}}}},"PrivateNote":{"type":"object","properties":{"id":{"type":"string","description":"The private note id"},"content":{"type":"string","description":"The private note content"},"authorId":{"type":"string","description":"The author user id"},"targetId":{"type":"string","description":"The target user id"}}},"CreatePrivateNote":{"type":"object","properties":{"content":{"type":"string","description":"The private note content"},"authorId":{"type":"string","description":"The author user id"},"targetId":{"type":"string","description":"The target user id"}}},"UpdatePrivateNote":{"type":"object","properties":{"id":{"type":"string","description":"The private note id"},"content":{"type":"string","description":"The private note content"}}},"RoomTags":{"type":"object","properties":{"tags":{"description":"the room tags list","type":"array","items":{"type":"string"}}}},"RoomTagCreation":{"type":"object","properties":{"content":{"type":"array","description":"the room tags strings","items":{"type":"string"}},"roomId":{"type":"string","description":"the room id"}}},"RoomTagsUpdate":{"type":"object","properties":{"content":{"type":"array","description":"the room tags strings","items":{"type":"string"}}}},"sms":{"type":"object","properties":{"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"text":{"type":"string"}}},"UserInfo":{"type":"object","properties":{"uid":{"type":"string","description":"the user id"},"givenName":{"type":"string","description":"the user given name"},"sn":{"type":"string","description":"the user surname"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"The message describing the internal error"}}}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_UNAUTHORIZED","error":"Unauthorized"}}}},"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_MISSING_PARAMS","error":"Properties are missing in the request body"}}}},"Forbidden":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_FORBIDDEN","error":"Forbidden"}}}},"Conflict":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"error":"Conflict"}}}},"PermanentRedirect":{"description":"Permanent Redirect","headers":{"Location":{"schema":{"type":"string","description":"URL to use for recdirect"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_UNKNOWN","error":"This non-standard endpoint has been removed"}}}},"NotFound":{"description":"Private note not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_NOT_FOUND","error":"Not Found"}}}},"Unrecognized":{"description":"Unrecognized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_UNRECOGNIZED","error":"Unrecognized"}}}},"Created":{"description":"Created"},"NoContent":{"description":"operation successful and no content returned"},"InternalError":{"description":"Internal error"}},"parameters":{"target_userid":{"name":"target_userid","in":"path","required":true,"description":"the target user id","schema":{"type":"string"}},"user_id":{"name":"user_id","in":"query","description":"the author user id","required":true,"schema":{"type":"string"}},"target_user_id":{"name":"target_user_id","in":"query","description":"the target user id","required":true,"schema":{"type":"string"}},"private_note_id":{"name":"private_note_id","in":"path","description":"the private note id","required":true,"schema":{"type":"string"}},"roomId":{"in":"path","name":"roomId","description":"the room id","required":true,"schema":{"type":"string"}},"userId":{"in":"path","name":"userId","description":"the user id","required":true,"schema":{"type":"string"}}}},"security":[{"bearerAuth":[]}],"paths":{"/_matrix/identity/v2":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2"}},"/_matrix/identity/v2/hash_details":{"get":{"tags":["Federated identity service"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2hash_details"}},"/_matrix/identity/v2/lookup":{"post":{"tags":["Federated identity service"],"description":"Extends https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2lookup to display inactive users and 3PID users","requestBody":{"description":"Object containing hashes of mails/phones to search","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string","description":"List of (hashed) addresses to lookup"}},"algorithm":{"type":"string","description":"Algorithm the client is using to encode the addresses"},"pepper":{"type":"string","description":"Pepper from '/hash_details'"}},"required":["addresses","algorithm","pepper"]},"example":{"addresses":["4kenr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","nlo35_T5fzSGZzJApqu8lgIudJvmOQtDaHtr-I4rU7I"],"algorithm":"sha256","pepper":"matrixrocks"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"mappings":{"type":"object","additionalProperties":{"type":"string"},"description":"List of active accounts"},"inactive_mappings":{"type":"object","additionalProperties":{"type":"string"},"description":"List of inactive accounts"},"third_party_mappings":{"type":"object","description":"List of hashed addresses by identity server hostname","properties":{"hostname":{"type":"object","properties":{"actives":{"type":"array","items":{"type":"string","description":"List of (hashed) active accounts addresses matching request body addresses"}},"inactives":{"type":"array","items":{"type":"string","description":"List of (hashed) inactive accounts addresses matching request body addresses"}}}}}}}},"example":{"mappings":{"4kenr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc":"@dwho:company.com"},"inactive_mappings":{"nlo35_T5fzSGZzJApqu8lgIudJvmOQtDaHtr-I4rU7I":"@rtyler:company.com"},"third_party_mappings":{"identity1.example.com":{"actives":["78jnr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","gtr42_T5fzSGZzJAmlp5lgIudJvmOQtDaHtr-I4rU7I"],"inactives":["qfgt57N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","lnbc8_T5fzSGZzJAmlp5lgIudJvmOQtDaHtr-I4rU7I"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/identity/v2/account":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2account"}},"/_matrix/identity/v2/account/register":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2accountregister"}},"/_matrix/identity/v2/account/logout":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2accountlogout"}},"/_matrix/identity/v2/terms":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2terms"}},"/_matrix/identity/v2/validate/email/requestToken":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2validateemailrequesttoken"}},"/_matrix/identity/v2/validate/email/submitToken":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2validateemailsubmittoken"}},"/_matrix/identity/versions":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityversions"}},"/_twake/identity/v1/lookup/match":{"post":{"tags":["Identity server"],"description":"Looks up the Organization User IDs which match value sent","requestBody":{"description":"Object containing detail for the search and the returned data","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"array","items":{"type":"string","description":"List of fields to search in (uid, mail,...)"}},"fields":{"type":"array","items":{"type":"string","description":"List of fields to return for matching users (uid, mail, mobile, displayName, givenName, cn, sn)"}},"val":{"type":"string","description":"Optional value to search"},"limit":{"type":"integer","description":"Optional max number of result to return (default 30)"},"offset":{"type":"integer","description":"Optional offset for pagination"}},"required":["scope","fields"]},"example":{"scope":["mail","uid"],"fields":["uid","displayName","sn","givenName","mobile"],"val":"rtyler","limit":3}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string","description":"Matrix address"},"uid":{"type":"string","description":"id of a matching user"},"mail":{"type":"string","description":"email address of a matching user"}}},"description":"List of users that match"}}},"example":{"matches":[{"uid":"dwho","mail":"dwho@badwolf.com"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/_twake/identity/v1/lookup/diff":{"post":{"tags":["Identity server"],"description":"Looks up the Organization User IDs updated since X","requestBody":{"description":"Object containing the timestamp","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"since":{"type":"integer","description":"timestamp"},"fields":{"type":"array","items":{"type":"string","description":"List of fields to return for matching users"}},"limit":{"type":"integer","description":"Optional max number of result to return (default 30)"},"offset":{"type":"integer","description":"Optional offset for pagination"}}},"example":{"since":1685074279,"fields":["uid","mail"],"limit":3}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string","description":"Matrix address"},"timestamp":{"type":"integer","description":"current server timestamp"},"uid":{"type":"string","description":"id of a matching user"},"mail":{"type":"string","description":"email address of a matching user"}}},"description":"List of users that match"}}},"example":{"matches":[{"uid":"dwho","mail":"dwho@badwolf.com"}]}}}}}}},"/_twake/recoveryWords":{"get":{"tags":["Vault API"],"description":"Allow for the connected user to retrieve its recovery words","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"words":{"type":"string","description":"Recovery words of the connected user"}}},"example":{"words":"This is the recovery sentence of rtyler"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Connected user has no recovery sentence"}}},"example":{"error":"User has no recovery sentence"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Connected user has multiple recovery sentence"}}},"example":{"error":"User has more than one recovery sentence"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Vault API"],"description":"Store connected user recovery words in database","requestBody":{"description":"Object containing the recovery words of the connected user","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"words":{"type":"string","description":"The recovery words of the connected user"}},"required":["words"]},"example":{"words":"This is the recovery sentence of rtyler"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Message indicating that words have been successfully saved"}},"example":{"message":"Saved recovery words sucessfully"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Vault API"],"description":"Delete the user recovery words in the database","responses":{"204":{"description":"Delete success"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Connected user has no recovery sentence"}}},"example":{"error":"User has no recovery sentence"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/.well-knwon/matrix/client":{"get":{"tags":["Auto configuration"],"description":"Get server metadata for auto configuration","responses":{"200":{"description":"Give server metadata","content":{"application/json":{"schema":{"type":"object","properties":{"m.homeserver":{"type":"object","properties":{"base_url":{"type":"string","description":"Base URL of Matrix server"}}},"m.identity_server":{"type":"object","properties":{"base_url":{"type":"string","description":"Base URL of Identity server"}}},"m.federated_identity_services":{"type":"object","properties":{"base_urls":{"type":"array","items":{"type":"string","description":"Base URL of Federated identity service"},"description":"Available Federated identity services Base URL list"}}},"t.server":{"type":"object","properties":{"base_url":{"type":"string","description":"Base URL of Identity server"},"server_name":{"type":"string","description":"Domain handled by Matrix server"}}},"m.integrations":{"type":"object","properties":{"jitsi":{"type":"object","properties":{"preferredDomain":{"type":"string","description":"Jitsi's preffered domain"},"baseUrl":{"type":"string","description":"URL of Jitsi server"},"useJwt":{"type":"boolean","description":"True if Jitsi server requires a JWT"},"jwt":{"type":"object","properties":{"algorithm":{"type":"string","description":"algorithm used to generate JWT"},"secret":{"type":"string","description":"password of JWTs"},"issuer":{"type":"string","description":"issuer of JWTs"}}}}}}},"m.authentication":{"type":"object","properties":{"issuer":{"type":"string","description":"URL of OIDC issuer"}}}}},"example":{"m.homeserver":{"base_url":"matrix.example.com"},"m.identity_server":{"base_url":"global-id-server.twake.app"},"m.federated_identity_services":{"base_urls":["global-federated_identity_service.twake.app","other-federated-identity-service.twake.app"]},"m.integrations":{"jitsi":{"baseUrl":"https://jitsi.example.com/","preferredDomain":"jitsi.example.com","useJwt":false}},"m.authentication":{"issuer":"https://auth.example.com"},"t.server":{"base_url":"https://tom.example.com","server_name":"example.com"}}}}}}}},"/_matrix/identity/v2/lookups":{"post":{"tags":["Federated identity service"],"description":"Implements https://github.com/guimard/matrix-spec-proposals/blob/unified-identity-service/proposals/4004-unified-identity-service-view.md","requestBody":{"description":"Object containing hashes to store in federated identity service database","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mappings":{"type":"object","description":"List of hashed addresses by identity server hostname","properties":{"hostname":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string"},"active":{"type":"number"}}}}}},"algorithm":{"type":"string","description":"Algorithm the client is using to encode the addresses"},"pepper":{"type":"string","description":"Pepper from '/hash_details'"}},"required":["addresses","algorithm","pepper"]},"example":{"mappings":{"identity1.example.com":[{"hash":"4kenr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","active":1},{"hash":"nlo35_T5fzSGZzJApqu8lgIudJvmOQtDaHtr-I4rU7I","active":0}]},"algorithm":"sha256","pepper":"matrixrocks"}}}},"responses":{"201":{"description":"Success"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/app/v1/transactions/{txnId}":{"put":{"parameters":[{"in":"path","name":"txnId","required":true,"schema":{"type":"integer"},"description":"The transaction id"}],"tags":["Application server"],"description":"Implements https://spec.matrix.org/v1.6/application-service-api/#put_matrixappv1transactionstxnid","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"308":{"$ref":"#/components/responses/PermanentRedirect"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/app/v1/users/{userId}":{"get":{"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"integer"},"description":"The user id"}],"tags":["Application server"],"description":"Implements https://spec.matrix.org/v1.6/application-service-api/#get_matrixappv1usersuserid","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/app/v1/rooms/{roomAlias}":{"get":{"parameters":[{"in":"path","name":"roomAlias","required":true,"schema":{"type":"integer"},"description":"The room alias"}],"tags":["Application server"],"description":"Implements https://spec.matrix.org/v1.6/application-service-api/#get_matrixappv1roomsroomalias","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/v1/activecontacts":{"get":{"tags":["Active contacts"],"description":"Get the list of active contacts","responses":{"200":{"description":"Active contacts found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveContacts"}}}},"401":{"description":"user is unauthorized"},"404":{"description":"Active contacts not found"},"500":{"description":"Internal error"}}},"post":{"tags":["Active contacts"],"description":"Create or update the list of active contacts","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveContacts"}}}},"responses":{"201":{"description":"Active contacts saved"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"delete":{"tags":["Active contacts"],"description":"Delete the list of active contacts","responses":{"200":{"description":"Active contacts deleted"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error/"}}}},"/_twake/app/v1/rooms":{"post":{"tags":["Application server"],"description":"Implements https://www.notion.so/Automatic-channels-89ba6f97bc90474ca482a28cf3228d3e","requestBody":{"description":"Object containing room's details","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ldapFilter":{"type":"object","additionalProperties":true,"description":"An object containing keys/values to build a ldap filter"},"aliasName":{"type":"string","description":"The desired room alias local part. If aliasName is equal to foo the complete room alias will be"},"name":{"type":"string","description":"The room name"},"topic":{"type":"string","description":"A short message detailing what is currently being discussed in the room."},"visibility":{"type":"string","enum":["public","private"],"description":"visibility values:\n * `public` - The room will be shown in the published room list\n * `private` - Hide the room from the published room list\n"}},"required":["ldapFilter","aliasName"]},"example":{"ldapFilter":{"mail":"example@test.com","cn":"example"},"aliasName":"exp","name":"Example","topic":"This is an example of a room topic","visibility":"public"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"errcode":{"type":"string"},"error":{"type":"string"}},"additionalProperties":{"type":"string"},"description":"List of users uid not added to the new room due to an error"},"example":[{"uid":"test1","errcode":"M_FORBIDDEN","error":"The user has been banned from the room"},{"uid":"test2","errcode":"M_UNKNOWN","error":"Internal server error"}]}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"examples":{"example1":{"value":{"error":"Error field: Invalid value (property: name)"}},"example2":{"value":{"errcode":"M_NOT_JSON","error":"Not_json"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"examples":{"example1":{"value":{"error":"This room already exits in Twake database"}},"example2":{"value":{"errcode":"M_ROOM_IN_USE","error":"A room with alias foo already exists in Matrix database"}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/mutual_rooms/{target_userid}":{"get":{"tags":["Mutual Rooms"],"description":"Get the list of mutual rooms between two users","parameters":[{"$ref":"#/components/parameters/target_userid"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutualRooms"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"},"500":{"description":"Internal error"}}}},"/_twake/private_note":{"get":{"tags":["Private Note"],"description":"Get the private note made by the user for a target user","parameters":[{"$ref":"#/components/parameters/user_id"},{"$ref":"#/components/parameters/target_user_id"}],"responses":{"200":{"description":"Private note found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivateNote"}}}},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"404":{"description":"Private note not found"},"500":{"description":"Internal error"}}},"post":{"tags":["Private Note"],"description":"Create a private note for a target user","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePrivateNote"}}}},"responses":{"201":{"description":"Private note created"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"put":{"tags":["Private Note"],"description":"Update a private note","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePrivateNote"}}}},"responses":{"204":{"description":"Private note created"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/private_note/{private_note_id}":{"delete":{"tags":["Private Note"],"description":"Delete a private note","parameters":[{"$ref":"#/components/parameters/private_note_id"}],"responses":{"204":{"description":"Private note deleted"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/v1/room_tags/{roomId}":{"get":{"tags":["Room tags"],"description":"Get room tags","parameters":[{"$ref":"#/components/parameters/roomId"}],"responses":{"200":{"description":"Room tags found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomTags"}}}},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"put":{"tags":["Room tags"],"description":"Update room tags","parameters":[{"$ref":"#/components/parameters/roomId"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomTagsUpdate"}}}},"responses":{"204":{"description":"Room tags updated"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"delete":{"tags":["Room tags"],"description":"delete tags for a room","parameters":[{"$ref":"#/components/parameters/roomId"}],"responses":{"204":{"description":"Room tags deleted"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/v1/room_tags":{"post":{"tags":["Room tags"],"description":"Create room tags","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomTagCreation"}}}},"responses":{"201":{"description":"Room tags created"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/app/v1/search":{"post":{"tags":["Search Engine"],"description":"Search performs with OpenSearch on Tchat messages and rooms","requestBody":{"description":"Object containing search query details","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"searchValue":{"type":"string","description":"Value used to perform the search on rooms and messages data"}},"required":["searchValue"]},"example":{"searchValue":"hello"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"rooms":{"type":"array","description":"List of rooms whose name contains the search value","items":{"type":"object","properties":{"room_id":{"type":"string"},"name":{"type":"string"},"avatar_url":{"type":"string","description":"Url of the room's avatar"}}}},"messages":{"type":"array","description":"List of messages whose content or/and sender display name contain the search value","items":{"type":"object","properties":{"room_id":{"type":"string"},"event_id":{"type":"string","description":"Id of the message"},"content":{"type":"string"},"display_name":{"type":"string","description":"Sender display name"},"avatar_url":{"type":"string","description":"Sender's avatar url if it is a direct chat, otherwise it is the room's avatar url"},"room_name":{"type":"string","description":"Room's name in case of the message is not part of a direct chat"}}}},"mails":{"type":"array","description":"List of mails from Tmail whose meta or content contain the search value","items":{"type":"object","properties":{"attachments":{"type":"array","items":{"type":"object","properties":{"contentDisposition":{"type":"string"},"fileExtension":{"type":"string"},"fileName":{"type":"string"},"mediaType":{"type":"string"},"subtype":{"type":"string"},"textContent":{"type":"string"}}}},"bcc":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"cc":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"date":{"type":"string"},"from":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"hasAttachment":{"type":"boolean"},"headers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"htmlBody":{"type":"string"},"isAnswered":{"type":"boolean"},"isDeleted":{"type":"boolean"},"isDraft":{"type":"boolean"},"isFlagged":{"type":"boolean"},"isRecent":{"type":"boolean"},"isUnread":{"type":"boolean"},"mailboxId":{"type":"string"},"mediaType":{"type":"string"},"messageId":{"type":"string"},"mimeMessageID":{"type":"string"},"modSeq":{"type":"number"},"saveDate":{"type":"string"},"sentDate":{"type":"string"},"size":{"type":"number"},"subject":{"type":"array","items":{"type":"string"}},"subtype":{"type":"string"},"textBody":{"type":"string"},"threadId":{"type":"string"},"to":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"uid":{"type":"number"},"userFlags":{"type":"array","items":{"type":"string"}}}}}}},"example":{"rooms":[{"room_id":"!dYqMpBXVQgKWETVAtJ:example.com","name":"Hello world room","avatar_url":"mxc://linagora.com/IBGFusHnOOzCNfePjaIVHpgR"},{"room_id":"!dugSgNYwppGGoeJwYB:example.com","name":"Worldwide room","avatar_url":null}],"messages":[{"room_id":"!dYqMpBXVQgKWETVAtJ:example.com","event_id":"$c0hW6db_GUjk0NRBUuO12IyMpi48LE_tQK6sH3dkd1U","content":"Hello world","display_name":"Anakin Skywalker","avatar_url":"mxc://linagora.com/IBGFusHnOOzCNfePjaIVHpgR","room_name":"Hello world room"},{"room_id":"!ftGqINYwppGGoeJwYB:example.com","event_id":"$IUzFofxHCvvoHJ-k2nfx7OlWOO8AuPvlHHqkeJLzxJ8","content":"Hello world my friends in direct chat","display_name":"Luke Skywalker","avatar_url":"mxc://matrix.org/wefh34uihSDRGhw34"}],"mails":[{"id":"message1","attachments":[{"contentDisposition":"attachment","fileExtension":"jpg","fileName":"image1.jpg","mediaType":"image/jpeg","textContent":"A beautiful galaxy far, far away."}],"bcc":[{"address":"okenobi@example.com","domain":"example.com","name":"Obi-Wan Kenobi"}],"cc":[{"address":"pamidala@example.com","domain":"example.com","name":"Padme Amidala"}],"date":"2024-02-24T10:15:00Z","from":[{"address":"dmaul@example.com","domain":"example.com","name":"Dark Maul"}],"hasAttachment":true,"headers":[{"name":"Header5","value":"Value5"},{"name":"Header6","value":"Value6"}],"htmlBody":"

A beautiful galaxy far, far away.

","isAnswered":true,"isDeleted":false,"isDraft":false,"isFlagged":true,"isRecent":true,"isUnread":false,"mailboxId":"mailbox3","mediaType":"image/jpeg","messageId":"message3","mimeMessageID":"mimeMessageID3","modSeq":98765,"saveDate":"2024-02-24T10:15:00Z","sentDate":"2024-02-24T10:15:00Z","size":4096,"subject":["Star Wars Message 3"],"subtype":"subtype3","textBody":"A beautiful galaxy far, far away.","threadId":"thread3","to":[{"address":"kren@example.com","domain":"example.com","name":"Kylo Ren"}],"uid":987654,"userFlags":["Flag4","Flag5"]}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/app/v1/opensearch/restore":{"post":{"tags":["Search Engine"],"description":"Restore OpenSearch indexes using Matrix homeserver database","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"204":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/sms":{"post":{"requestBody":{"description":"SMS object","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/sms"}}}},"tags":["SMS"],"description":"Send an SMS to a phone number","responses":{"200":{"description":"SMS sent successfully"},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/_twake/v1/user_info/{userId}":{"get":{"tags":["User Info"],"description":"Get user info","parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"User info found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInfo"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"404":{"description":"User info not found"},"500":{"description":"Internal server error"}}}}},"tags":[]} \ No newline at end of file +{"openapi":"3.0.0","info":{"title":"Twake on Matrix APIs documentation","version":"0.0.1","description":"This is The documentation of all available APIs of this repository"},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"MatrixError":{"type":"object","properties":{"errcode":{"type":"string","description":"A Matrix error code"},"error":{"type":"string","description":"A human-readable error message"}},"required":["error"]},"ActiveContacts":{"type":"object","description":"the list of active contacts","properties":{"contacts":{"type":"string","description":"active contacts"}}},"MutualRooms":{"type":"array","items":{"type":"object","properties":{"roomId":{"type":"string","description":"the room id"},"name":{"type":"string","description":"the room name"},"topic":{"type":"string","description":"the room topic"},"room_type":{"type":"string","description":"the room type"}}}},"PrivateNote":{"type":"object","properties":{"id":{"type":"string","description":"The private note id"},"content":{"type":"string","description":"The private note content"},"authorId":{"type":"string","description":"The author user id"},"targetId":{"type":"string","description":"The target user id"}}},"CreatePrivateNote":{"type":"object","properties":{"content":{"type":"string","description":"The private note content"},"authorId":{"type":"string","description":"The author user id"},"targetId":{"type":"string","description":"The target user id"}}},"UpdatePrivateNote":{"type":"object","properties":{"id":{"type":"string","description":"The private note id"},"content":{"type":"string","description":"The private note content"}}},"RoomTags":{"type":"object","properties":{"tags":{"description":"the room tags list","type":"array","items":{"type":"string"}}}},"RoomTagCreation":{"type":"object","properties":{"content":{"type":"array","description":"the room tags strings","items":{"type":"string"}},"roomId":{"type":"string","description":"the room id"}}},"RoomTagsUpdate":{"type":"object","properties":{"content":{"type":"array","description":"the room tags strings","items":{"type":"string"}}}},"sms":{"type":"object","properties":{"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"text":{"type":"string"}}},"UserInfo":{"type":"object","properties":{"uid":{"type":"string","description":"the user id"},"givenName":{"type":"string","description":"the user given name"},"sn":{"type":"string","description":"the user surname"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"The message describing the internal error"}}}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_UNAUTHORIZED","error":"Unauthorized"}}}},"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_MISSING_PARAMS","error":"Properties are missing in the request body"}}}},"Forbidden":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_FORBIDDEN","error":"Forbidden"}}}},"Conflict":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"error":"Conflict"}}}},"PermanentRedirect":{"description":"Permanent Redirect","headers":{"Location":{"schema":{"type":"string","description":"URL to use for recdirect"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_UNKNOWN","error":"This non-standard endpoint has been removed"}}}},"NotFound":{"description":"Private note not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_NOT_FOUND","error":"Not Found"}}}},"Unrecognized":{"description":"Unrecognized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"example":{"errcode":"M_UNRECOGNIZED","error":"Unrecognized"}}}},"Created":{"description":"Created"},"NoContent":{"description":"operation successful and no content returned"},"InternalError":{"description":"Internal error"}},"parameters":{"target_userid":{"name":"target_userid","in":"path","required":true,"description":"the target user id","schema":{"type":"string"}},"user_id":{"name":"user_id","in":"query","description":"the author user id","required":true,"schema":{"type":"string"}},"target_user_id":{"name":"target_user_id","in":"query","description":"the target user id","required":true,"schema":{"type":"string"}},"private_note_id":{"name":"private_note_id","in":"path","description":"the private note id","required":true,"schema":{"type":"string"}},"roomId":{"in":"path","name":"roomId","description":"the room id","required":true,"schema":{"type":"string"}},"userId":{"in":"path","name":"userId","description":"the user id","required":true,"schema":{"type":"string"}}}},"security":[{"bearerAuth":[]}],"paths":{"/_matrix/identity/v2":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2"}},"/_matrix/identity/v2/hash_details":{"get":{"tags":["Federated identity service"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2hash_details"}},"/_matrix/identity/v2/lookup":{"post":{"tags":["Federated identity service"],"description":"Extends https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2lookup to display inactive users and 3PID users","requestBody":{"description":"Object containing hashes of mails/phones to search","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"addresses":{"type":"array","items":{"type":"string","description":"List of (hashed) addresses to lookup"}},"algorithm":{"type":"string","description":"Algorithm the client is using to encode the addresses"},"pepper":{"type":"string","description":"Pepper from '/hash_details'"}},"required":["addresses","algorithm","pepper"]},"example":{"addresses":["4kenr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","nlo35_T5fzSGZzJApqu8lgIudJvmOQtDaHtr-I4rU7I"],"algorithm":"sha256","pepper":"matrixrocks"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"mappings":{"type":"object","additionalProperties":{"type":"string"},"description":"List of active accounts"},"inactive_mappings":{"type":"object","additionalProperties":{"type":"string"},"description":"List of inactive accounts"},"third_party_mappings":{"type":"object","description":"List of hashed addresses by identity server hostname","properties":{"hostname":{"type":"object","properties":{"actives":{"type":"array","items":{"type":"string","description":"List of (hashed) active accounts addresses matching request body addresses"}},"inactives":{"type":"array","items":{"type":"string","description":"List of (hashed) inactive accounts addresses matching request body addresses"}}}}}}}},"example":{"mappings":{"4kenr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc":"@dwho:company.com"},"inactive_mappings":{"nlo35_T5fzSGZzJApqu8lgIudJvmOQtDaHtr-I4rU7I":"@rtyler:company.com"},"third_party_mappings":{"identity1.example.com":{"actives":["78jnr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","gtr42_T5fzSGZzJAmlp5lgIudJvmOQtDaHtr-I4rU7I"],"inactives":["qfgt57N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","lnbc8_T5fzSGZzJAmlp5lgIudJvmOQtDaHtr-I4rU7I"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/identity/v2/account":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2account"}},"/_matrix/identity/v2/account/register":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2accountregister"}},"/_matrix/identity/v2/account/logout":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2accountlogout"}},"/_matrix/identity/v2/terms":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityv2terms"}},"/_matrix/identity/v2/validate/email/requestToken":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2validateemailrequesttoken"}},"/_matrix/identity/v2/validate/email/submitToken":{"post":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#post_matrixidentityv2validateemailsubmittoken"}},"/_matrix/identity/versions":{"get":{"tags":["Identity server"],"description":"Implements https://spec.matrix.org/v1.6/identity-service-api/#get_matrixidentityversions"}},"/_twake/identity/v1/lookup/match":{"post":{"tags":["Identity server"],"description":"Looks up the Organization User IDs which match value sent","requestBody":{"description":"Object containing detail for the search and the returned data","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"array","items":{"type":"string","description":"List of fields to search in (uid, mail,...)"}},"fields":{"type":"array","items":{"type":"string","description":"List of fields to return for matching users (uid, mail, mobile, displayName, givenName, cn, sn)"}},"val":{"type":"string","description":"Optional value to search"},"limit":{"type":"integer","description":"Optional max number of result to return (default 30)"},"offset":{"type":"integer","description":"Optional offset for pagination"}},"required":["scope","fields"]},"example":{"scope":["mail","uid"],"fields":["uid","displayName","sn","givenName","mobile"],"val":"rtyler","limit":3}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string","description":"Matrix address"},"uid":{"type":"string","description":"id of a matching user"},"mail":{"type":"string","description":"email address of a matching user"}}},"description":"List of users that match"}}},"example":{"matches":[{"uid":"dwho","mail":"dwho@badwolf.com"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/_twake/identity/v1/lookup/diff":{"post":{"tags":["Identity server"],"description":"Looks up the Organization User IDs updated since X","requestBody":{"description":"Object containing the timestamp","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"since":{"type":"integer","description":"timestamp"},"fields":{"type":"array","items":{"type":"string","description":"List of fields to return for matching users"}},"limit":{"type":"integer","description":"Optional max number of result to return (default 30)"},"offset":{"type":"integer","description":"Optional offset for pagination"}}},"example":{"since":1685074279,"fields":["uid","mail"],"limit":3}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string","description":"Matrix address"},"timestamp":{"type":"integer","description":"current server timestamp"},"uid":{"type":"string","description":"id of a matching user"},"mail":{"type":"string","description":"email address of a matching user"}}},"description":"List of users that match"}}},"example":{"matches":[{"uid":"dwho","mail":"dwho@badwolf.com"}]}}}}}}},"/_twake/recoveryWords":{"get":{"tags":["Vault API"],"description":"Allow for the connected user to retrieve its recovery words","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"words":{"type":"string","description":"Recovery words of the connected user"}}},"example":{"words":"This is the recovery sentence of rtyler"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Connected user has no recovery sentence"}}},"example":{"error":"User has no recovery sentence"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Connected user has multiple recovery sentence"}}},"example":{"error":"User has more than one recovery sentence"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"tags":["Vault API"],"description":"Store connected user recovery words in database","requestBody":{"description":"Object containing the recovery words of the connected user","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"words":{"type":"string","description":"The recovery words of the connected user"}},"required":["words"]},"example":{"words":"This is the recovery sentence of rtyler"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Message indicating that words have been successfully saved"}},"example":{"message":"Saved recovery words sucessfully"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"tags":["Vault API"],"description":"Delete the user recovery words in the database","responses":{"204":{"description":"Delete success"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Connected user has no recovery sentence"}}},"example":{"error":"User has no recovery sentence"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["Vault API"],"description":"Update stored connected user recovery words in database","requestBody":{"description":"Object containing the recovery words of the connected user","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"words":{"type":"string","description":"The new recovery words of the connected user"}},"required":["words"]},"example":{"words":"This is the updated recovery sentence of rtyler"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Message indicating that words have been successfully updated"}},"example":{"message":"Updated recovery words sucessfully"}}}}},"400":{"description":"Bad request"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/.well-knwon/matrix/client":{"get":{"tags":["Auto configuration"],"description":"Get server metadata for auto configuration","responses":{"200":{"description":"Give server metadata","content":{"application/json":{"schema":{"type":"object","properties":{"m.homeserver":{"type":"object","properties":{"base_url":{"type":"string","description":"Base URL of Matrix server"}}},"m.identity_server":{"type":"object","properties":{"base_url":{"type":"string","description":"Base URL of Identity server"}}},"m.federated_identity_services":{"type":"object","properties":{"base_urls":{"type":"array","items":{"type":"string","description":"Base URL of Federated identity service"},"description":"Available Federated identity services Base URL list"}}},"t.server":{"type":"object","properties":{"base_url":{"type":"string","description":"Base URL of Identity server"},"server_name":{"type":"string","description":"Domain handled by Matrix server"}}},"m.integrations":{"type":"object","properties":{"jitsi":{"type":"object","properties":{"preferredDomain":{"type":"string","description":"Jitsi's preffered domain"},"baseUrl":{"type":"string","description":"URL of Jitsi server"},"useJwt":{"type":"boolean","description":"True if Jitsi server requires a JWT"},"jwt":{"type":"object","properties":{"algorithm":{"type":"string","description":"algorithm used to generate JWT"},"secret":{"type":"string","description":"password of JWTs"},"issuer":{"type":"string","description":"issuer of JWTs"}}}}}}},"m.authentication":{"type":"object","properties":{"issuer":{"type":"string","description":"URL of OIDC issuer"}}}}},"example":{"m.homeserver":{"base_url":"matrix.example.com"},"m.identity_server":{"base_url":"global-id-server.twake.app"},"m.federated_identity_services":{"base_urls":["global-federated_identity_service.twake.app","other-federated-identity-service.twake.app"]},"m.integrations":{"jitsi":{"baseUrl":"https://jitsi.example.com/","preferredDomain":"jitsi.example.com","useJwt":false}},"m.authentication":{"issuer":"https://auth.example.com"},"t.server":{"base_url":"https://tom.example.com","server_name":"example.com"}}}}}}}},"/_matrix/identity/v2/lookups":{"post":{"tags":["Federated identity service"],"description":"Implements https://github.com/guimard/matrix-spec-proposals/blob/unified-identity-service/proposals/4004-unified-identity-service-view.md","requestBody":{"description":"Object containing hashes to store in federated identity service database","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mappings":{"type":"object","description":"List of hashed addresses by identity server hostname","properties":{"hostname":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string"},"active":{"type":"number"}}}}}},"algorithm":{"type":"string","description":"Algorithm the client is using to encode the addresses"},"pepper":{"type":"string","description":"Pepper from '/hash_details'"}},"required":["addresses","algorithm","pepper"]},"example":{"mappings":{"identity1.example.com":[{"hash":"4kenr7N9drpCJ4AfalmlGQVsOn3o2RHjkADUpXJWZUc","active":1},{"hash":"nlo35_T5fzSGZzJApqu8lgIudJvmOQtDaHtr-I4rU7I","active":0}]},"algorithm":"sha256","pepper":"matrixrocks"}}}},"responses":{"201":{"description":"Success"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/app/v1/transactions/{txnId}":{"put":{"parameters":[{"in":"path","name":"txnId","required":true,"schema":{"type":"integer"},"description":"The transaction id"}],"tags":["Application server"],"description":"Implements https://spec.matrix.org/v1.6/application-service-api/#put_matrixappv1transactionstxnid","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"308":{"$ref":"#/components/responses/PermanentRedirect"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/app/v1/users/{userId}":{"get":{"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"integer"},"description":"The user id"}],"tags":["Application server"],"description":"Implements https://spec.matrix.org/v1.6/application-service-api/#get_matrixappv1usersuserid","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_matrix/app/v1/rooms/{roomAlias}":{"get":{"parameters":[{"in":"path","name":"roomAlias","required":true,"schema":{"type":"integer"},"description":"The room alias"}],"tags":["Application server"],"description":"Implements https://spec.matrix.org/v1.6/application-service-api/#get_matrixappv1roomsroomalias","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/v1/activecontacts":{"get":{"tags":["Active contacts"],"description":"Get the list of active contacts","responses":{"200":{"description":"Active contacts found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveContacts"}}}},"401":{"description":"user is unauthorized"},"404":{"description":"Active contacts not found"},"500":{"description":"Internal error"}}},"post":{"tags":["Active contacts"],"description":"Create or update the list of active contacts","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveContacts"}}}},"responses":{"201":{"description":"Active contacts saved"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"delete":{"tags":["Active contacts"],"description":"Delete the list of active contacts","responses":{"200":{"description":"Active contacts deleted"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error/"}}}},"/_twake/app/v1/rooms":{"post":{"tags":["Application server"],"description":"Implements https://www.notion.so/Automatic-channels-89ba6f97bc90474ca482a28cf3228d3e","requestBody":{"description":"Object containing room's details","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ldapFilter":{"type":"object","additionalProperties":true,"description":"An object containing keys/values to build a ldap filter"},"aliasName":{"type":"string","description":"The desired room alias local part. If aliasName is equal to foo the complete room alias will be"},"name":{"type":"string","description":"The room name"},"topic":{"type":"string","description":"A short message detailing what is currently being discussed in the room."},"visibility":{"type":"string","enum":["public","private"],"description":"visibility values:\n * `public` - The room will be shown in the published room list\n * `private` - Hide the room from the published room list\n"}},"required":["ldapFilter","aliasName"]},"example":{"ldapFilter":{"mail":"example@test.com","cn":"example"},"aliasName":"exp","name":"Example","topic":"This is an example of a room topic","visibility":"public"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"errcode":{"type":"string"},"error":{"type":"string"}},"additionalProperties":{"type":"string"},"description":"List of users uid not added to the new room due to an error"},"example":[{"uid":"test1","errcode":"M_FORBIDDEN","error":"The user has been banned from the room"},{"uid":"test2","errcode":"M_UNKNOWN","error":"Internal server error"}]}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"examples":{"example1":{"value":{"error":"Error field: Invalid value (property: name)"}},"example2":{"value":{"errcode":"M_NOT_JSON","error":"Not_json"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixError"},"examples":{"example1":{"value":{"error":"This room already exits in Twake database"}},"example2":{"value":{"errcode":"M_ROOM_IN_USE","error":"A room with alias foo already exists in Matrix database"}}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/mutual_rooms/{target_userid}":{"get":{"tags":["Mutual Rooms"],"description":"Get the list of mutual rooms between two users","parameters":[{"$ref":"#/components/parameters/target_userid"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutualRooms"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"},"500":{"description":"Internal error"}}}},"/_twake/private_note":{"get":{"tags":["Private Note"],"description":"Get the private note made by the user for a target user","parameters":[{"$ref":"#/components/parameters/user_id"},{"$ref":"#/components/parameters/target_user_id"}],"responses":{"200":{"description":"Private note found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivateNote"}}}},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"404":{"description":"Private note not found"},"500":{"description":"Internal error"}}},"post":{"tags":["Private Note"],"description":"Create a private note for a target user","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePrivateNote"}}}},"responses":{"201":{"description":"Private note created"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"put":{"tags":["Private Note"],"description":"Update a private note","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePrivateNote"}}}},"responses":{"204":{"description":"Private note created"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/private_note/{private_note_id}":{"delete":{"tags":["Private Note"],"description":"Delete a private note","parameters":[{"$ref":"#/components/parameters/private_note_id"}],"responses":{"204":{"description":"Private note deleted"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/v1/room_tags/{roomId}":{"get":{"tags":["Room tags"],"description":"Get room tags","parameters":[{"$ref":"#/components/parameters/roomId"}],"responses":{"200":{"description":"Room tags found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomTags"}}}},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"put":{"tags":["Room tags"],"description":"Update room tags","parameters":[{"$ref":"#/components/parameters/roomId"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomTagsUpdate"}}}},"responses":{"204":{"description":"Room tags updated"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}},"delete":{"tags":["Room tags"],"description":"delete tags for a room","parameters":[{"$ref":"#/components/parameters/roomId"}],"responses":{"204":{"description":"Room tags deleted"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/v1/room_tags":{"post":{"tags":["Room tags"],"description":"Create room tags","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoomTagCreation"}}}},"responses":{"201":{"description":"Room tags created"},"400":{"description":"Bad request"},"401":{"description":"user is unauthorized"},"500":{"description":"Internal error"}}}},"/_twake/app/v1/search":{"post":{"tags":["Search Engine"],"description":"Search performs with OpenSearch on Tchat messages and rooms","requestBody":{"description":"Object containing search query details","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"searchValue":{"type":"string","description":"Value used to perform the search on rooms and messages data"}},"required":["searchValue"]},"example":{"searchValue":"hello"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"rooms":{"type":"array","description":"List of rooms whose name contains the search value","items":{"type":"object","properties":{"room_id":{"type":"string"},"name":{"type":"string"},"avatar_url":{"type":"string","description":"Url of the room's avatar"}}}},"messages":{"type":"array","description":"List of messages whose content or/and sender display name contain the search value","items":{"type":"object","properties":{"room_id":{"type":"string"},"event_id":{"type":"string","description":"Id of the message"},"content":{"type":"string"},"display_name":{"type":"string","description":"Sender display name"},"avatar_url":{"type":"string","description":"Sender's avatar url if it is a direct chat, otherwise it is the room's avatar url"},"room_name":{"type":"string","description":"Room's name in case of the message is not part of a direct chat"}}}},"mails":{"type":"array","description":"List of mails from Tmail whose meta or content contain the search value","items":{"type":"object","properties":{"attachments":{"type":"array","items":{"type":"object","properties":{"contentDisposition":{"type":"string"},"fileExtension":{"type":"string"},"fileName":{"type":"string"},"mediaType":{"type":"string"},"subtype":{"type":"string"},"textContent":{"type":"string"}}}},"bcc":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"cc":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"date":{"type":"string"},"from":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"hasAttachment":{"type":"boolean"},"headers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}}},"htmlBody":{"type":"string"},"isAnswered":{"type":"boolean"},"isDeleted":{"type":"boolean"},"isDraft":{"type":"boolean"},"isFlagged":{"type":"boolean"},"isRecent":{"type":"boolean"},"isUnread":{"type":"boolean"},"mailboxId":{"type":"string"},"mediaType":{"type":"string"},"messageId":{"type":"string"},"mimeMessageID":{"type":"string"},"modSeq":{"type":"number"},"saveDate":{"type":"string"},"sentDate":{"type":"string"},"size":{"type":"number"},"subject":{"type":"array","items":{"type":"string"}},"subtype":{"type":"string"},"textBody":{"type":"string"},"threadId":{"type":"string"},"to":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"domain":{"type":"string"},"name":{"type":"string"}}}},"uid":{"type":"number"},"userFlags":{"type":"array","items":{"type":"string"}}}}}}},"example":{"rooms":[{"room_id":"!dYqMpBXVQgKWETVAtJ:example.com","name":"Hello world room","avatar_url":"mxc://linagora.com/IBGFusHnOOzCNfePjaIVHpgR"},{"room_id":"!dugSgNYwppGGoeJwYB:example.com","name":"Worldwide room","avatar_url":null}],"messages":[{"room_id":"!dYqMpBXVQgKWETVAtJ:example.com","event_id":"$c0hW6db_GUjk0NRBUuO12IyMpi48LE_tQK6sH3dkd1U","content":"Hello world","display_name":"Anakin Skywalker","avatar_url":"mxc://linagora.com/IBGFusHnOOzCNfePjaIVHpgR","room_name":"Hello world room"},{"room_id":"!ftGqINYwppGGoeJwYB:example.com","event_id":"$IUzFofxHCvvoHJ-k2nfx7OlWOO8AuPvlHHqkeJLzxJ8","content":"Hello world my friends in direct chat","display_name":"Luke Skywalker","avatar_url":"mxc://matrix.org/wefh34uihSDRGhw34"}],"mails":[{"id":"message1","attachments":[{"contentDisposition":"attachment","fileExtension":"jpg","fileName":"image1.jpg","mediaType":"image/jpeg","textContent":"A beautiful galaxy far, far away."}],"bcc":[{"address":"okenobi@example.com","domain":"example.com","name":"Obi-Wan Kenobi"}],"cc":[{"address":"pamidala@example.com","domain":"example.com","name":"Padme Amidala"}],"date":"2024-02-24T10:15:00Z","from":[{"address":"dmaul@example.com","domain":"example.com","name":"Dark Maul"}],"hasAttachment":true,"headers":[{"name":"Header5","value":"Value5"},{"name":"Header6","value":"Value6"}],"htmlBody":"

A beautiful galaxy far, far away.

","isAnswered":true,"isDeleted":false,"isDraft":false,"isFlagged":true,"isRecent":true,"isUnread":false,"mailboxId":"mailbox3","mediaType":"image/jpeg","messageId":"message3","mimeMessageID":"mimeMessageID3","modSeq":98765,"saveDate":"2024-02-24T10:15:00Z","sentDate":"2024-02-24T10:15:00Z","size":4096,"subject":["Star Wars Message 3"],"subtype":"subtype3","textBody":"A beautiful galaxy far, far away.","threadId":"thread3","to":[{"address":"kren@example.com","domain":"example.com","name":"Kylo Ren"}],"uid":987654,"userFlags":["Flag4","Flag5"]}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/app/v1/opensearch/restore":{"post":{"tags":["Search Engine"],"description":"Restore OpenSearch indexes using Matrix homeserver database","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"204":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"405":{"$ref":"#/components/responses/Unrecognized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/_twake/sms":{"post":{"requestBody":{"description":"SMS object","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/sms"}}}},"tags":["SMS"],"description":"Send an SMS to a phone number","responses":{"200":{"description":"SMS sent successfully"},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/_twake/v1/user_info/{userId}":{"get":{"tags":["User Info"],"description":"Get user info","parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"User info found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInfo"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"404":{"description":"User info not found"},"500":{"description":"Internal server error"}}}}},"tags":[]} \ No newline at end of file diff --git a/packages/tom-server/src/vault-api/controllers/vault.test.ts b/packages/tom-server/src/vault-api/controllers/vault.test.ts index 88ae7dac..4fbf4acd 100644 --- a/packages/tom-server/src/vault-api/controllers/vault.test.ts +++ b/packages/tom-server/src/vault-api/controllers/vault.test.ts @@ -2,7 +2,12 @@ import { type NextFunction, type Request, type Response } from 'express' import { type TwakeDB } from '../../db' import { type tokenDetail } from '../middlewares/auth' import { VaultAPIError, type expressAppHandler } from '../utils' -import { getRecoveryWords, methodNotAllowed, saveRecoveryWords } from './vault' +import { + getRecoveryWords, + methodNotAllowed, + saveRecoveryWords, + updateRecoveryWords +} from './vault' const words = 'This is a test sentence' @@ -14,7 +19,8 @@ describe('Vault controllers', () => { const dbManager: Partial = { get: jest.fn(), insert: jest.fn(), - deleteWhere: jest.fn() + deleteWhere: jest.fn(), + update: jest.fn() } let mockRequest: ITestRequest let mockResponse: Partial @@ -69,6 +75,7 @@ describe('Vault controllers', () => { // Testing saveRecoveryWords it('should return response with status code 201 on save success', async () => { jest.spyOn(dbManager, 'insert').mockResolvedValue([{ words }]) + jest.spyOn(dbManager, 'get').mockResolvedValue([]) const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB) handler(mockRequest as Request, mockResponse as Response, nextFunction) await new Promise(process.nextTick) @@ -78,12 +85,34 @@ describe('Vault controllers', () => { it('should call next function to throw error on saving failed', async () => { const errorMsg = 'Insert failed' jest.spyOn(dbManager, 'insert').mockRejectedValue(new Error(errorMsg)) + jest.spyOn(dbManager, 'get').mockResolvedValue([]) const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB) handler(mockRequest as Request, mockResponse as Response, nextFunction) await new Promise(process.nextTick) expect(nextFunction).toHaveBeenCalledWith(new Error(errorMsg)) }) + it('should return a 409 response when recovery words already exists', async () => { + jest + .spyOn(dbManager, 'get') + .mockResolvedValue([{ words: 'Another sentence for the same user' }]) + const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB) + handler(mockRequest as Request, mockResponse as Response, nextFunction) + await new Promise(process.nextTick) + expect(mockResponse.statusCode).toEqual(409) + expect(dbManager.insert).not.toHaveBeenCalled() + }) + + it('should return a 400 error if the body does not contain recovery words', async () => { + jest.spyOn(dbManager, 'get').mockResolvedValue([]) + const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB) + const emptyRequest = { ...mockRequest, body: {} } + handler(emptyRequest as Request, mockResponse as Response, nextFunction) + await new Promise(process.nextTick) + expect(mockResponse.statusCode).toEqual(400) + expect(dbManager.insert).not.toHaveBeenCalled() + }) + // Testing getRecoveryWords it('should return response with status code 200 on get success', async () => { @@ -127,4 +156,31 @@ describe('Vault controllers', () => { await new Promise(process.nextTick) expect(nextFunction).toHaveBeenCalledWith(new Error(errorMsg)) }) + + it('should return a 200 response on update success', async () => { + jest + .spyOn(dbManager, 'get') + .mockResolvedValue([{ userId: 'test', words: 'some recovery words' }]) + const handler: expressAppHandler = updateRecoveryWords(dbManager as TwakeDB) + handler(mockRequest as Request, mockResponse as Response, nextFunction) + await new Promise(process.nextTick) + expect(mockResponse.statusCode).toEqual(200) + }) + + it('should throw a 404 error when no recovery words were found', async () => { + jest.spyOn(dbManager, 'get').mockResolvedValue([]) + const handler: expressAppHandler = updateRecoveryWords(dbManager as TwakeDB) + handler(mockRequest as Request, mockResponse as Response, nextFunction) + await new Promise(process.nextTick) + expect(mockResponse.statusCode).toEqual(404) + }) + + it('should throw a 400 error when the body does not contain recovery words', async () => { + jest.spyOn(dbManager, 'get').mockResolvedValue([{ userId: 'test' }]) + const handler: expressAppHandler = updateRecoveryWords(dbManager as TwakeDB) + const emptyRequest = { ...mockRequest, body: {} } + handler(emptyRequest as Request, mockResponse as Response, nextFunction) + await new Promise(process.nextTick) + expect(mockResponse.statusCode).toEqual(400) + }) }) diff --git a/packages/tom-server/src/vault-api/controllers/vault.ts b/packages/tom-server/src/vault-api/controllers/vault.ts index 049f0fc2..5a199345 100644 --- a/packages/tom-server/src/vault-api/controllers/vault.ts +++ b/packages/tom-server/src/vault-api/controllers/vault.ts @@ -1,4 +1,7 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +/* eslint-disable no-useless-return */ import { type TwakeDB } from '../../db' +import { type Collections } from '../../types' import { VaultAPIError, type expressAppHandler } from '../utils' export type VaultController = (db: TwakeDB) => expressAppHandler @@ -7,20 +10,38 @@ export const methodNotAllowed: expressAppHandler = (req, res, next) => { throw new VaultAPIError('Method not allowed', 405) } +/** + * Save use recovery words + * + * @param {TwakeDB} db - the database instance + * @retuns {expressAppHandler} - the express handler + */ export const saveRecoveryWords = (db: TwakeDB): expressAppHandler => { - return (req, res, next) => { - const data: Record = { - userId: req.token.content.sub, - words: req.body.words - } - // @ts-expect-error 'recoveryWords' isn't declared in Collection - db.insert('recoveryWords', data) - .then((_) => { - res.status(201).json({ message: 'Saved recovery words sucessfully' }) - }) - .catch((err) => { - next(err) + return async (req, res, next) => { + const { words } = req.body + const userId = req.token.content.sub + + try { + if (words === undefined || words.length === 0) { + res.status(400).json({ error: 'Missing recovery words' }) + return + } + + const data = await db.get('recoveryWords' as Collections, ['words'], { + userId }) + + if (data.length > 0) { + res.status(409).json({ error: 'User already has recovery words' }) + return + } else { + await db.insert('recoveryWords' as Collections, { userId, words }) + res.status(201).json({ message: 'Saved recovery words successfully' }) + return + } + } catch (err) { + next(err) + } } } @@ -79,3 +100,44 @@ export const deleteRecoveryWords = (db: TwakeDB): expressAppHandler => { }) } } + +/** + * Update recovery words in database + * + * @param {TwakeDB} db - the database instance + * @returns {expressAppHandler} - the express controller handler + */ +export const updateRecoveryWords = (db: TwakeDB): expressAppHandler => { + return async (req, res, next) => { + const userId: string = req.token.content.sub + const { words } = req.body + + try { + if (words === undefined || words.length === 0) { + res.status(400).json({ message: 'Missing recovery sentence' }) + return + } + + const data = await db.get('recoveryWords' as Collections, ['words'], { + userId + }) + + if (data.length === 0) { + res.status(404).json({ message: 'User has no recovery sentence' }) + return + } + + await db.update( + 'recoveryWords' as Collections, + { words }, + 'userId', + userId + ) + + res.status(200).json({ message: 'Updated recovery words successfully' }) + return + } catch (err) { + next(err) + } + } +} diff --git a/packages/tom-server/src/vault-api/index.test.ts b/packages/tom-server/src/vault-api/index.test.ts index de57e287..de56554f 100644 --- a/packages/tom-server/src/vault-api/index.test.ts +++ b/packages/tom-server/src/vault-api/index.test.ts @@ -121,9 +121,9 @@ describe('Vault API server', () => { }) it('reject not allowed method with 405', async () => { - const response = await request(app).put(endpoint) + const response = await request(app).patch(endpoint) expect(response.statusCode).toBe(405) - expect(response.body).toStrictEqual({ + expect(response.body).toStrictEqual({ error: 'Method not allowed' }) }) @@ -145,7 +145,7 @@ describe('Vault API server', () => { .set('Authorization', `Bearer ${accessToken}`) expect(response.statusCode).toBe(201) expect(response.body).toStrictEqual({ - message: 'Saved recovery words sucessfully' + message: 'Saved recovery words successfully' }) }) @@ -206,7 +206,7 @@ describe('Vault API server', () => { .set('Authorization', `Bearer ${unsavedToken}`) expect(response.statusCode).toBe(201) expect(response.body).toStrictEqual({ - message: 'Saved recovery words sucessfully' + message: 'Saved recovery words successfully' }) await removeUserInAccessTokenTable(unsavedToken) await removeUserInRecoveryWordsTable(matrixServerResponseBody.user_id) @@ -236,6 +236,18 @@ describe('Vault API server', () => { }) }) + it('should update words in the dabase if the connected user have some', async () => { + const response = await request(app) + .put(endpoint) + .send({ words }) + .set('Authorization', `Bearer ${accessToken}`) + + expect(response.statusCode).toBe(200) + expect(response.body).toStrictEqual({ + message: 'Updated recovery words successfully' + }) + }) + it('should reject if more than 100 requests are done in less than 10 seconds on get words', async () => { let response let token diff --git a/packages/tom-server/src/vault-api/index.ts b/packages/tom-server/src/vault-api/index.ts index be462b84..47d019f8 100644 --- a/packages/tom-server/src/vault-api/index.ts +++ b/packages/tom-server/src/vault-api/index.ts @@ -7,6 +7,7 @@ import { getRecoveryWords, methodNotAllowed, saveRecoveryWords, + updateRecoveryWords, type VaultController } from './controllers/vault' import isAuth, { type tokenDetail } from './middlewares/auth' @@ -155,6 +156,49 @@ export default class TwakeVaultAPI { * $ref: '#/components/responses/InternalServerError' */ .delete(...this._middlewares(deleteRecoveryWords)) + /** + * @openapi + * '/_twake/recoveryWords': + * put: + * tags: + * - Vault API + * description: Update stored connected user recovery words in database + * requestBody: + * description: Object containing the recovery words of the connected user + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * words: + * type: string + * description: The new recovery words of the connected user + * required: + * - words + * example: + * words: This is the updated recovery sentence of rtyler + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Message indicating that words have been successfully updated + * example: + * message: Updated recovery words sucessfully + * 401: + * $ref: '#/components/responses/Unauthorized' + * 500: + * $ref: '#/components/responses/InternalServerError' + * 400: + * description: Bad request + */ + .put(...this._middlewares(updateRecoveryWords)) .all(allowCors, methodNotAllowed, errorMiddleware) }