diff --git a/README.md b/README.md index 905c01eb..8fea1336 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ the following section will show a step-by-step guide on how to get your own emai [3. Contributing Guide](#contributing) -[4. API](#api) +[4. API](docs/api.md) -[5. OAuth2/OpenID Connect](#oauth) +[5. OAuth2/OpenID Connect](docs/oauth.md) ## General Architecture @@ -721,800 +721,6 @@ swaks --to e1@d1.localhost --from hey@google.com --server 127.0.0.1:20381 Now open http://localhost:1080/, you should see the test email. -## API - -SimpleLogin current API clients are Chrome/Firefox/Safari extension and mobile (iOS/Android) app. -These clients rely on `API Code` for authentication. - -Once the `Api Code` is obtained, either via user entering it (in Browser extension case) or by logging in (in Mobile case), -the client includes the `api code` in `Authentication` header in almost all requests. - -For some endpoints, the `hostname` should be passed in query string. `hostname` is the the URL hostname (cf https://en.wikipedia.org/wiki/URL), for ex if URL is http://www.example.com/index.html then the hostname is `www.example.com`. This information is important to know where an alias is used in order to suggest user the same alias if they want to create on alias on the same website in the future. - -If error, the API returns 4** with body containing the error message, for example: - -```json -{ - "error": "request body cannot be empty" -} -``` - -The error message could be displayed to user as-is, for example for when user exceeds their alias quota. -Some errors should be fixed during development however: for example error like `request body cannot be empty` is there to catch development error and should never be shown to user. - -All following endpoint return `401` status code if the API Key is incorrect. - -### Account endpoints - -#### POST /api/auth/login - -Input: -- email -- password -- device: device name. Used to create the API Key. Should be humanly readable so user can manage later on the "API Key" page. - -Output: -- name: user name, could be an empty string -- email: user email -- mfa_enabled: boolean -- mfa_key: only useful when user enables MFA. In this case, user needs to enter their OTP token in order to login. -- api_key: if MFA is not enabled, the `api key` is returned right away. - -The `api_key` is used in all subsequent requests. It's empty if MFA is enabled. -If user hasn't enabled MFA, `mfa_key` is empty. - -Return 403 if user has enabled FIDO. The client can display a message to suggest user to use the `API Key` instead. - -#### POST /api/auth/mfa - -Input: -- mfa_token: OTP token that user enters -- mfa_key: MFA key obtained in previous auth request, e.g. /api/auth/login -- device: the device name, used to create an ApiKey associated with this device - -Output: -- name: user name, could be an empty string -- api_key: if MFA is not enabled, the `api key` is returned right away. -- email: user email - -The `api_key` is used in all subsequent requests. It's empty if MFA is enabled. -If user hasn't enabled MFA, `mfa_key` is empty. - -#### POST /api/auth/facebook - -Input: -- facebook_token: Facebook access token -- device: device name. Used to create the API Key. Should be humanly readable so user can manage later on the "API Key" page. - -Output: Same output as for `/api/auth/login` endpoint - -#### POST /api/auth/google - -Input: -- google_token: Google access token -- device: device name. Used to create the API Key. Should be humanly readable so user can manage later on the "API Key" page. - -Output: Same output as for `/api/auth/login` endpoint - -#### POST /api/auth/register - -Input: -- email -- password - -Output: 200 means user is going to receive an email that contains an *activation code*. User needs to enter this code to confirm their account -> next endpoint. - -#### POST /api/auth/activate - -Input: -- email -- code: the activation code - -Output: -- 200: account is activated. User can login now -- 400: wrong email, code -- 410: wrong code too many times. User needs to ask for an reactivation -> next endpoint - -#### POST /api/auth/reactivate - -Input: -- email - -Output: -- 200: user is going to receive an email that contains the activation code. - -#### POST /api/auth/forgot_password - -Input: -- email - -Output: always return 200, even if email doesn't exist. User need to enter correctly their email. - -#### GET /api/user_info - -Given the API Key, return user name and whether user is premium. -This endpoint could be used to validate the api key. - -Input: -- `Authentication` header that contains the api key - -Output: if api key is correct, return a json with user name and whether user is premium, for example: - -```json -{ - "name": "John Wick", - "is_premium": false, - "email": "john@wick.com", - "in_trial": true, - "profile_picture_url": "https://profile.png" -} -``` - -If api key is incorrect, return 401. - -#### PATCH /api/user_info - -Update user info - -Input: -- profile_picture: the profile picture in base64. Setting to `null` remove the current profile picture. -- name - -Output: same as GET /api/user_info - - -#### POST /api/api_key - -Create a new API Key - -Input: -- `Authentication` header that contains the api key -- Or the correct cookie is set, i.e. user is already logged in on the web -- device: device's name - -Output -- 401 if user is not authenticated -- 201 with the `api_key` - -```json -{ - "api_key": "long string" -} -``` - -#### GET /api/logout - -Log user out - -Input: -- `Authentication` header that contains the api key -- Or the correct cookie is set, i.e. user is already logged in on the web - -Output: -- 401 if user is not authenticated -- 200 if success - - -### Alias endpoints - -#### GET /api/v4/alias/options - -User alias info and suggestion. Used by the first extension screen when user opens the extension. - -Input: -- `Authentication` header that contains the api key -- (Optional but recommended) `hostname` passed in query string. - -Output: a json with the following field: -- can_create: boolean. Whether user can create new alias -- suffixes: list of `[suffix, signed-suffix]`. List of alias `suffix` that user can use. The `signed-suffix` is necessary to avoid request tampering. -- prefix_suggestion: string. Suggestion for the `alias prefix`. Usually this is the website name extracted from `hostname`. If no `hostname`, then the `prefix_suggestion` is empty. -- recommendation: optional field, dictionary. If an alias is already used for this website, the recommendation will be returned. There are 2 subfields in `recommendation`: `alias` which is the recommended alias and `hostname` is the website on which this alias is used before. - -For ex: -```json -{ - "can_create": true, - "prefix_suggestion": "", - "suffixes": [ - [ - "@ab.cd", - "@ab.cd.Xq2BOA.zBebBB-QYikFkbPZ9CPKGpJ2-PU" - ], - [ - ".yeah@local1.localhost", - ".yeah@local1.localhost.Xq2BOA.dM9gyHyHcSXuJ8ps4i3wpJZ_Frw" - ] - ] -} -``` - -#### POST /api/v3/alias/custom/new - -Create a new custom alias. - -Input: -- `Authentication` header that contains the api key -- (Optional but recommended) `hostname` passed in query string -- Request Message Body in json (`Content-Type` is `application/json`) - - alias_prefix: string. The first part of the alias that user can choose. - - signed_suffix: should be one of the suffixes returned in the `GET /api/v4/alias/options` endpoint. - - mailbox_ids: list of mailbox_id that "owns" this alias - - (Optional) note: alias note - - (Optional) name: alias name - -Output: -If success, 201 with the new alias info. Use the same format as in GET /api/aliases/:alias_id - -#### POST /api/alias/random/new - -Create a new random alias. - -Input: -- `Authentication` header that contains the api key -- (Optional but recommended) `hostname` passed in query string -- (Optional) mode: either `uuid` or `word`. By default, use the user setting when creating new random alias. -- Request Message Body in json (`Content-Type` is `application/json`) - - (Optional) note: alias note - -Output: -If success, 201 with the new alias info. Use the same format as in GET /api/aliases/:alias_id - -#### GET /api/v2/aliases - -Get user aliases. - -Input: -- `Authentication` header that contains the api key -- `page_id` in query. Used for the pagination. The endpoint returns maximum 20 aliases for each page. `page_id` starts at 0. -- (Optional) query: included in request body. Some frameworks might prevent GET request having a non-empty body, in this case this endpoint also supports POST. - -Output: -If success, 200 with the list of aliases. Each alias has the following fields: - -- id -- email -- name -- enabled -- creation_timestamp -- note -- nb_block -- nb_forward -- nb_reply -- support_pgp: whether an alias can support PGP, i.e. when one of alias's mailboxes supports PGP. -- disable_pgp: whether the PGP is disabled on this alias. - This field should only be used when `support_pgp` is true. - By setting `disable_pgp=true`, a user can explicitly disable PGP on an alias even its mailboxes support PGP. -- mailbox: obsolete, should use `mailboxes` instead. - - id - - email -- mailboxes: list of mailbox, contains at least 1 mailbox. - - id - - email -- (nullable) latest_activity: - - action: forward|reply|block|bounced - - timestamp - - contact: - - email - - name - - reverse_alias - -Here's an example: - -```json -{ - "aliases": [ - { - "creation_date": "2020-04-06 17:57:14+00:00", - "creation_timestamp": 1586195834, - "email": "prefix1.cat@sl.local", - "name": "A Name", - "enabled": true, - "id": 3, - "mailbox": { - "email": "a@b.c", - "id": 1 - }, - "mailboxes": [ - { - "email": "m1@cd.ef", - "id": 2 - }, - { - "email": "john@wick.com", - "id": 1 - } - ], - "latest_activity": { - "action": "forward", - "contact": { - "email": "c1@example.com", - "name": null, - "reverse_alias": "\"c1 at example.com\" " - }, - "timestamp": 1586195834 - }, - "nb_block": 0, - "nb_forward": 1, - "nb_reply": 0, - "note": null - } - ] -} -``` - -#### GET /api/aliases/:alias_id - -Get alias info - -Input: -- `Authentication` header that contains the api key -- `alias_id` in url - -Output: -Alias info, use the same format as in /api/v2/aliases. For example: - -```json -{ - "creation_date": "2020-04-06 17:57:14+00:00", - "creation_timestamp": 1586195834, - "email": "prefix1.cat@sl.local", - "name": "A Name", - "enabled": true, - "id": 3, - "mailbox": { - "email": "a@b.c", - "id": 1 - }, - "mailboxes": [ - { - "email": "m1@cd.ef", - "id": 2 - }, - { - "email": "john@wick.com", - "id": 1 - } - ], - "latest_activity": { - "action": "forward", - "contact": { - "email": "c1@example.com", - "name": null, - "reverse_alias": "\"c1 at example.com\" " - }, - "timestamp": 1586195834 - }, - "nb_block": 0, - "nb_forward": 1, - "nb_reply": 0, - "note": null -} -``` - -#### DELETE /api/aliases/:alias_id - -Delete an alias - -Input: -- `Authentication` header that contains the api key -- `alias_id` in url. - -Output: -If success, 200. - - -```json -{ - "deleted": true -} -``` - -#### POST /api/aliases/:alias_id/toggle - -Enable/disable alias - -Input: -- `Authentication` header that contains the api key -- `alias_id` in url. - -Output: -If success, 200 along with the new alias status: - -```json -{ - "enabled": false -} -``` - -#### GET /api/aliases/:alias_id/activities - -Get activities for a given alias. - -Input: -- `Authentication` header that contains the api key -- `alias_id`: the alias id, passed in url. -- `page_id` used in request query (`?page_id=0`). The endpoint returns maximum 20 aliases for each page. `page_id` starts at 0. - -Output: -If success, 200 with the list of activities, for example: - -```json -{ - "activities": [ - { - "action": "reply", - "from": "yes_meo_chat@sl.local", - "timestamp": 1580903760, - "to": "marketing@example.com", - "reverse_alias": "\"marketing at example.com\" " - }, - { - "action": "reply", - "from": "yes_meo_chat@sl.local", - "timestamp": 1580903760, - "to": "marketing@example.com", - "reverse_alias": "\"marketing at example.com\" " - } - ] -} -``` - -#### PUT /api/aliases/:alias_id - -Update alias info. - -Input: -- `Authentication` header that contains the api key -- `alias_id` in url. -- (optional) `note` in request body -- (optional) `mailbox_id` in request body -- (optional) `name` in request body -- (optional) `mailbox_ids` in request body: array of mailbox_id -- (optional) `disable_pgp` in request body: boolean - -Output: -If success, return 200 - -#### GET /api/aliases/:alias_id/contacts - -Get contacts for a given alias. - -Input: -- `Authentication` header that contains the api key -- `alias_id`: the alias id, passed in url. -- `page_id` used in request query (`?page_id=0`). The endpoint returns maximum 20 contacts for each page. `page_id` starts at 0. - -Output: -If success, 200 with the list of contacts, for example: - -```json -{ - "contacts": [ - { - "id": 1, - "contact": "marketing@example.com", - "creation_date": "2020-02-21 11:35:00+00:00", - "creation_timestamp": 1582284900, - "last_email_sent_date": null, - "last_email_sent_timestamp": null, - "reverse_alias": "marketing at example.com " - }, - { - "id": 2, - "contact": "newsletter@example.com", - "creation_date": "2020-02-21 11:35:00+00:00", - "creation_timestamp": 1582284900, - "last_email_sent_date": "2020-02-21 11:35:00+00:00", - "last_email_sent_timestamp": 1582284900, - "reverse_alias": "newsletter at example.com " - } - ] -} -``` - -Please note that last_email_sent_timestamp and last_email_sent_date can be null. - -#### POST /api/aliases/:alias_id/contacts - -Create a new contact for an alias. - -Input: -- `Authentication` header that contains the api key -- `alias_id` in url. -- `contact` in request body - -Output: -If success, return 201 -Return 409 if contact is already added. - -``` -{ - "id": 1, - "contact": "First Last ", - "creation_date": "2020-03-14 11:52:41+00:00", - "creation_timestamp": 1584186761, - "last_email_sent_date": null, - "last_email_sent_timestamp": null, - "reverse_alias": "First Last first@example.com " -} -``` - -### Mailbox endpoints - -#### GET /api/v2/mailboxes - -Get user's mailboxes, including unverified ones. - -Input: -- `Authentication` header that contains the api key - -Output: -List of mailboxes. Each mailbox has id, email, default, creation_timestamp field - -```json -{ - "mailboxes": [ - { - "email": "a@b.c", - "id": 1, - "default": true, - "creation_timestamp": 1590918512, - "nb_alias": 10, - "verified": true - }, - { - "email": "m1@example.com", - "id": 2, - "default": false, - "creation_timestamp": 1590918512, - "nb_alias": 0, - "verified": false - } - ] -} -``` - -#### POST /api/mailboxes - -Create a new mailbox - -Input: -- `Authentication` header that contains the api key -- email: the new mailbox address - -Output: -- 201 along with the following response if new mailbox is created successfully. User is going to receive a verification email. - - id: integer - - email: the mailbox email address - - verified: boolean. - - default: whether is the default mailbox. User cannot delete the default mailbox -- 400 with error message otherwise. The error message can be displayed to user. - -#### DELETE /api/mailboxes/:mailbox_id - -Delete a mailbox. User cannot delete the default mailbox - -Input: -- `Authentication` header that contains the api key -- `mailbox_id`: in url - -Output: -- 200 if deleted successfully -- 400 if error - -#### PUT /api/mailboxes/:mailbox_id - -Update a mailbox. - -Input: -- `Authentication` header that contains the api key -- `mailbox_id`: in url -- (optional) `default`: boolean. Set a mailbox as default mailbox. -- (optional) `email`: email address. Change a mailbox email address. -- (optional) `cancel_email_change`: boolean. Cancel mailbox email change. - -Output: -- 200 if updated successfully -- 400 if error - - -### Contact endpoints - -#### DELETE /api/contacts/:contact_id - -Delete a contact - -Input: -- `Authentication` header that contains the api key -- `contact_id` in url. - -Output: -If success, 200. - - -```json -{ - "deleted": true -} -``` - -### Notification endpoints -#### GET /api/notifications - -Get notifications - -Input: -- `Authentication` in header: the api key -- page in url: the page number, starts at 0 - -Output: -- more: whether there's more notifications -- notifications: list of notification, each notification has: - - id - - message: the message in html - - read: whether the user has read the notification - - created_at: when the notification is created - -For example - -```json -{ - "more": false, - "notifications": [ - { - "created_at": "2 minutes ago", - "id": 1, - "message": "Hey!", - "read": false - } - ] -} -``` - -#### POST /api/notifications/:notification_id - -Mark a notification as read - -Input: -- `Authentication` in header: the api key -- notification_id in url: the page number, starts at 0 - -Output: -200 if success - -### Settings endpoints - -#### GET /api/setting - -Return user setting - -```json -{ - "alias_generator": "uuid", - "notification": true, - "random_alias_default_domain": "sl.local" -} -``` - -#### PATCH /api/setting - -Update user setting. All input fields are optional. - -Input: -- alias_generator (string): uuid or word -- notification (boolean): true or false -- random_alias_default_domain (string): one of the domains returned by `GET /api/setting/domains` - -Output: same as `GET /api/setting` - -#### GET /api/setting/domains - -Return domains that user can use to create random alias - -```json -[ - [ - true, - "d1.test" - ], - [ - true, - "d2.test" - ], - [ - true, - "sl.local" - ], - [ - false, - "ab.cd" - ] -] -``` - - -### Misc endpoints -#### POST /api/apple/process_payment - -Process payment receipt - -Input: -- `Authentication` in header: the api key -- `receipt_data` in body: the receipt_data base64Encoded returned by StoreKit, i.e. `rawReceiptData.base64EncodedString` -- (optional) `is_macapp` in body: if this field is present, the request is sent from the MacApp (Safari Extension) and not iOS app. - -Output: -200 if user is upgraded successfully -4** if any error. - - -## OAuth - -SL currently supports code and implicit flow. - -#### Code flow - -To trigger the code flow locally, you can go to the following url after running `python server.py`: - -``` -http://localhost:7777/oauth/authorize?client_id=client-id&state=123456&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A7000%2Fcallback&state=random_string -``` - -You should see there the authorization page where user is asked for permission to share their data. Once user approves, user is redirected to this url with an `authorization code`: `http://localhost:7000/callback?state=123456&code=the_code` - -Next, exchange the code to get the token with `{code}` replaced by the code obtained in previous step. The `http` tool used here is https://httpie.org - -``` -http -f -a client-id:client-secret http://localhost:7777/oauth/token grant_type=authorization_code code={code} -``` - -This should return an `access token` that allows to get user info via the following command. Again, `http` tool is used. - -``` -http http://localhost:7777/oauth/user_info 'Authorization:Bearer {token}' -``` - -#### Implicit flow - -Similar to code flow, except for the the `access token` which we we get back with the redirection. -For implicit flow, the url is - -``` -http://localhost:7777/oauth/authorize?client_id=client-id&state=123456&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3A7000%2Fcallback&state=random_string -``` - -#### OpenID and OAuth2 response_type & scope - -According to the sharing web blog titled [Diagrams of All The OpenID Connect Flows](https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660), we should pay attention to: - -- `response_type` can be either `code, token, id_token` or any combination of those attributes. -- `scope` might contain `openid` - -Below are the potential combinations that are taken into account in SL until now: - -``` -response_type=code - scope: - with `openid` in scope, return `id_token` at /token: OK - without: OK - -response_type=token - scope: - with and without `openid`, nothing to do: OK - -response_type=id_token - return `id_token` in /authorization endpoint - -response_type=id_token token - return `id_token` in addition to `access_token` in /authorization endpoint - -response_type=id_token code - return `id_token` in addition to `authorization_code` in /authorization endpoint - -``` - - ## ❤️ Contributors Thanks go to these wonderful people: diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..9f876214 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,726 @@ +## API + +SimpleLogin current API clients are Chrome/Firefox/Safari extension and mobile (iOS/Android) app. +These clients rely on `API Code` for authentication. + +Once the `Api Code` is obtained, either via user entering it (in Browser extension case) or by logging in (in Mobile case), +the client includes the `api code` in `Authentication` header in almost all requests. + +For some endpoints, the `hostname` should be passed in query string. `hostname` is the the URL hostname (cf https://en.wikipedia.org/wiki/URL), for ex if URL is http://www.example.com/index.html then the hostname is `www.example.com`. This information is important to know where an alias is used in order to suggest user the same alias if they want to create on alias on the same website in the future. + +If error, the API returns 4** with body containing the error message, for example: + +```json +{ + "error": "request body cannot be empty" +} +``` + +The error message could be displayed to user as-is, for example for when user exceeds their alias quota. +Some errors should be fixed during development however: for example error like `request body cannot be empty` is there to catch development error and should never be shown to user. + +All following endpoint return `401` status code if the API Key is incorrect. + +### Account endpoints + +#### POST /api/auth/login + +Input: +- email +- password +- device: device name. Used to create the API Key. Should be humanly readable so user can manage later on the "API Key" page. + +Output: +- name: user name, could be an empty string +- email: user email +- mfa_enabled: boolean +- mfa_key: only useful when user enables MFA. In this case, user needs to enter their OTP token in order to login. +- api_key: if MFA is not enabled, the `api key` is returned right away. + +The `api_key` is used in all subsequent requests. It's empty if MFA is enabled. +If user hasn't enabled MFA, `mfa_key` is empty. + +Return 403 if user has enabled FIDO. The client can display a message to suggest user to use the `API Key` instead. + +#### POST /api/auth/mfa + +Input: +- mfa_token: OTP token that user enters +- mfa_key: MFA key obtained in previous auth request, e.g. /api/auth/login +- device: the device name, used to create an ApiKey associated with this device + +Output: +- name: user name, could be an empty string +- api_key: if MFA is not enabled, the `api key` is returned right away. +- email: user email + +The `api_key` is used in all subsequent requests. It's empty if MFA is enabled. +If user hasn't enabled MFA, `mfa_key` is empty. + +#### POST /api/auth/facebook + +Input: +- facebook_token: Facebook access token +- device: device name. Used to create the API Key. Should be humanly readable so user can manage later on the "API Key" page. + +Output: Same output as for `/api/auth/login` endpoint + +#### POST /api/auth/google + +Input: +- google_token: Google access token +- device: device name. Used to create the API Key. Should be humanly readable so user can manage later on the "API Key" page. + +Output: Same output as for `/api/auth/login` endpoint + +#### POST /api/auth/register + +Input: +- email +- password + +Output: 200 means user is going to receive an email that contains an *activation code*. User needs to enter this code to confirm their account -> next endpoint. + +#### POST /api/auth/activate + +Input: +- email +- code: the activation code + +Output: +- 200: account is activated. User can login now +- 400: wrong email, code +- 410: wrong code too many times. User needs to ask for an reactivation -> next endpoint + +#### POST /api/auth/reactivate + +Input: +- email + +Output: +- 200: user is going to receive an email that contains the activation code. + +#### POST /api/auth/forgot_password + +Input: +- email + +Output: always return 200, even if email doesn't exist. User need to enter correctly their email. + +#### GET /api/user_info + +Given the API Key, return user name and whether user is premium. +This endpoint could be used to validate the api key. + +Input: +- `Authentication` header that contains the api key + +Output: if api key is correct, return a json with user name and whether user is premium, for example: + +```json +{ + "name": "John Wick", + "is_premium": false, + "email": "john@wick.com", + "in_trial": true, + "profile_picture_url": "https://profile.png" +} +``` + +If api key is incorrect, return 401. + +#### PATCH /api/user_info + +Update user info + +Input: +- profile_picture: the profile picture in base64. Setting to `null` remove the current profile picture. +- name + +Output: same as GET /api/user_info + + +#### POST /api/api_key + +Create a new API Key + +Input: +- `Authentication` header that contains the api key +- Or the correct cookie is set, i.e. user is already logged in on the web +- device: device's name + +Output +- 401 if user is not authenticated +- 201 with the `api_key` + +```json +{ + "api_key": "long string" +} +``` + +#### GET /api/logout + +Log user out + +Input: +- `Authentication` header that contains the api key +- Or the correct cookie is set, i.e. user is already logged in on the web + +Output: +- 401 if user is not authenticated +- 200 if success + + +### Alias endpoints + +#### GET /api/v4/alias/options + +User alias info and suggestion. Used by the first extension screen when user opens the extension. + +Input: +- `Authentication` header that contains the api key +- (Optional but recommended) `hostname` passed in query string. + +Output: a json with the following field: +- can_create: boolean. Whether user can create new alias +- suffixes: list of `[suffix, signed-suffix]`. List of alias `suffix` that user can use. The `signed-suffix` is necessary to avoid request tampering. +- prefix_suggestion: string. Suggestion for the `alias prefix`. Usually this is the website name extracted from `hostname`. If no `hostname`, then the `prefix_suggestion` is empty. +- recommendation: optional field, dictionary. If an alias is already used for this website, the recommendation will be returned. There are 2 subfields in `recommendation`: `alias` which is the recommended alias and `hostname` is the website on which this alias is used before. + +For ex: +```json +{ + "can_create": true, + "prefix_suggestion": "", + "suffixes": [ + [ + "@ab.cd", + "@ab.cd.Xq2BOA.zBebBB-QYikFkbPZ9CPKGpJ2-PU" + ], + [ + ".yeah@local1.localhost", + ".yeah@local1.localhost.Xq2BOA.dM9gyHyHcSXuJ8ps4i3wpJZ_Frw" + ] + ] +} +``` + +#### POST /api/v3/alias/custom/new + +Create a new custom alias. + +Input: +- `Authentication` header that contains the api key +- (Optional but recommended) `hostname` passed in query string +- Request Message Body in json (`Content-Type` is `application/json`) + - alias_prefix: string. The first part of the alias that user can choose. + - signed_suffix: should be one of the suffixes returned in the `GET /api/v4/alias/options` endpoint. + - mailbox_ids: list of mailbox_id that "owns" this alias + - (Optional) note: alias note + - (Optional) name: alias name + +Output: +If success, 201 with the new alias info. Use the same format as in GET /api/aliases/:alias_id + +#### POST /api/alias/random/new + +Create a new random alias. + +Input: +- `Authentication` header that contains the api key +- (Optional but recommended) `hostname` passed in query string +- (Optional) mode: either `uuid` or `word`. By default, use the user setting when creating new random alias. +- Request Message Body in json (`Content-Type` is `application/json`) + - (Optional) note: alias note + +Output: +If success, 201 with the new alias info. Use the same format as in GET /api/aliases/:alias_id + +#### GET /api/v2/aliases + +Get user aliases. + +Input: +- `Authentication` header that contains the api key +- `page_id` in query. Used for the pagination. The endpoint returns maximum 20 aliases for each page. `page_id` starts at 0. +- (Optional) query: included in request body. Some frameworks might prevent GET request having a non-empty body, in this case this endpoint also supports POST. + +Output: +If success, 200 with the list of aliases. Each alias has the following fields: + +- id +- email +- name +- enabled +- creation_timestamp +- note +- nb_block +- nb_forward +- nb_reply +- support_pgp: whether an alias can support PGP, i.e. when one of alias's mailboxes supports PGP. +- disable_pgp: whether the PGP is disabled on this alias. + This field should only be used when `support_pgp` is true. + By setting `disable_pgp=true`, a user can explicitly disable PGP on an alias even its mailboxes support PGP. +- mailbox: obsolete, should use `mailboxes` instead. + - id + - email +- mailboxes: list of mailbox, contains at least 1 mailbox. + - id + - email +- (nullable) latest_activity: + - action: forward|reply|block|bounced + - timestamp + - contact: + - email + - name + - reverse_alias + +Here's an example: + +```json +{ + "aliases": [ + { + "creation_date": "2020-04-06 17:57:14+00:00", + "creation_timestamp": 1586195834, + "email": "prefix1.cat@sl.local", + "name": "A Name", + "enabled": true, + "id": 3, + "mailbox": { + "email": "a@b.c", + "id": 1 + }, + "mailboxes": [ + { + "email": "m1@cd.ef", + "id": 2 + }, + { + "email": "john@wick.com", + "id": 1 + } + ], + "latest_activity": { + "action": "forward", + "contact": { + "email": "c1@example.com", + "name": null, + "reverse_alias": "\"c1 at example.com\" " + }, + "timestamp": 1586195834 + }, + "nb_block": 0, + "nb_forward": 1, + "nb_reply": 0, + "note": null + } + ] +} +``` + +#### GET /api/aliases/:alias_id + +Get alias info + +Input: +- `Authentication` header that contains the api key +- `alias_id` in url + +Output: +Alias info, use the same format as in /api/v2/aliases. For example: + +```json +{ + "creation_date": "2020-04-06 17:57:14+00:00", + "creation_timestamp": 1586195834, + "email": "prefix1.cat@sl.local", + "name": "A Name", + "enabled": true, + "id": 3, + "mailbox": { + "email": "a@b.c", + "id": 1 + }, + "mailboxes": [ + { + "email": "m1@cd.ef", + "id": 2 + }, + { + "email": "john@wick.com", + "id": 1 + } + ], + "latest_activity": { + "action": "forward", + "contact": { + "email": "c1@example.com", + "name": null, + "reverse_alias": "\"c1 at example.com\" " + }, + "timestamp": 1586195834 + }, + "nb_block": 0, + "nb_forward": 1, + "nb_reply": 0, + "note": null +} +``` + +#### DELETE /api/aliases/:alias_id + +Delete an alias + +Input: +- `Authentication` header that contains the api key +- `alias_id` in url. + +Output: +If success, 200. + + +```json +{ + "deleted": true +} +``` + +#### POST /api/aliases/:alias_id/toggle + +Enable/disable alias + +Input: +- `Authentication` header that contains the api key +- `alias_id` in url. + +Output: +If success, 200 along with the new alias status: + +```json +{ + "enabled": false +} +``` + +#### GET /api/aliases/:alias_id/activities + +Get activities for a given alias. + +Input: +- `Authentication` header that contains the api key +- `alias_id`: the alias id, passed in url. +- `page_id` used in request query (`?page_id=0`). The endpoint returns maximum 20 aliases for each page. `page_id` starts at 0. + +Output: +If success, 200 with the list of activities, for example: + +```json +{ + "activities": [ + { + "action": "reply", + "from": "yes_meo_chat@sl.local", + "timestamp": 1580903760, + "to": "marketing@example.com", + "reverse_alias": "\"marketing at example.com\" " + }, + { + "action": "reply", + "from": "yes_meo_chat@sl.local", + "timestamp": 1580903760, + "to": "marketing@example.com", + "reverse_alias": "\"marketing at example.com\" " + } + ] +} +``` + +#### PUT /api/aliases/:alias_id + +Update alias info. + +Input: +- `Authentication` header that contains the api key +- `alias_id` in url. +- (optional) `note` in request body +- (optional) `mailbox_id` in request body +- (optional) `name` in request body +- (optional) `mailbox_ids` in request body: array of mailbox_id +- (optional) `disable_pgp` in request body: boolean + +Output: +If success, return 200 + +#### GET /api/aliases/:alias_id/contacts + +Get contacts for a given alias. + +Input: +- `Authentication` header that contains the api key +- `alias_id`: the alias id, passed in url. +- `page_id` used in request query (`?page_id=0`). The endpoint returns maximum 20 contacts for each page. `page_id` starts at 0. + +Output: +If success, 200 with the list of contacts, for example: + +```json +{ + "contacts": [ + { + "id": 1, + "contact": "marketing@example.com", + "creation_date": "2020-02-21 11:35:00+00:00", + "creation_timestamp": 1582284900, + "last_email_sent_date": null, + "last_email_sent_timestamp": null, + "reverse_alias": "marketing at example.com " + }, + { + "id": 2, + "contact": "newsletter@example.com", + "creation_date": "2020-02-21 11:35:00+00:00", + "creation_timestamp": 1582284900, + "last_email_sent_date": "2020-02-21 11:35:00+00:00", + "last_email_sent_timestamp": 1582284900, + "reverse_alias": "newsletter at example.com " + } + ] +} +``` + +Please note that last_email_sent_timestamp and last_email_sent_date can be null. + +#### POST /api/aliases/:alias_id/contacts + +Create a new contact for an alias. + +Input: +- `Authentication` header that contains the api key +- `alias_id` in url. +- `contact` in request body + +Output: +If success, return 201 +Return 409 if contact is already added. + +``` +{ + "id": 1, + "contact": "First Last ", + "creation_date": "2020-03-14 11:52:41+00:00", + "creation_timestamp": 1584186761, + "last_email_sent_date": null, + "last_email_sent_timestamp": null, + "reverse_alias": "First Last first@example.com " +} +``` + +### Mailbox endpoints + +#### GET /api/v2/mailboxes + +Get user's mailboxes, including unverified ones. + +Input: +- `Authentication` header that contains the api key + +Output: +List of mailboxes. Each mailbox has id, email, default, creation_timestamp field + +```json +{ + "mailboxes": [ + { + "email": "a@b.c", + "id": 1, + "default": true, + "creation_timestamp": 1590918512, + "nb_alias": 10, + "verified": true + }, + { + "email": "m1@example.com", + "id": 2, + "default": false, + "creation_timestamp": 1590918512, + "nb_alias": 0, + "verified": false + } + ] +} +``` + +#### POST /api/mailboxes + +Create a new mailbox + +Input: +- `Authentication` header that contains the api key +- email: the new mailbox address + +Output: +- 201 along with the following response if new mailbox is created successfully. User is going to receive a verification email. + - id: integer + - email: the mailbox email address + - verified: boolean. + - default: whether is the default mailbox. User cannot delete the default mailbox +- 400 with error message otherwise. The error message can be displayed to user. + +#### DELETE /api/mailboxes/:mailbox_id + +Delete a mailbox. User cannot delete the default mailbox + +Input: +- `Authentication` header that contains the api key +- `mailbox_id`: in url + +Output: +- 200 if deleted successfully +- 400 if error + +#### PUT /api/mailboxes/:mailbox_id + +Update a mailbox. + +Input: +- `Authentication` header that contains the api key +- `mailbox_id`: in url +- (optional) `default`: boolean. Set a mailbox as default mailbox. +- (optional) `email`: email address. Change a mailbox email address. +- (optional) `cancel_email_change`: boolean. Cancel mailbox email change. + +Output: +- 200 if updated successfully +- 400 if error + + +### Contact endpoints + +#### DELETE /api/contacts/:contact_id + +Delete a contact + +Input: +- `Authentication` header that contains the api key +- `contact_id` in url. + +Output: +If success, 200. + + +```json +{ + "deleted": true +} +``` + +### Notification endpoints +#### GET /api/notifications + +Get notifications + +Input: +- `Authentication` in header: the api key +- page in url: the page number, starts at 0 + +Output: +- more: whether there's more notifications +- notifications: list of notification, each notification has: + - id + - message: the message in html + - read: whether the user has read the notification + - created_at: when the notification is created + +For example + +```json +{ + "more": false, + "notifications": [ + { + "created_at": "2 minutes ago", + "id": 1, + "message": "Hey!", + "read": false + } + ] +} +``` + +#### POST /api/notifications/:notification_id + +Mark a notification as read + +Input: +- `Authentication` in header: the api key +- notification_id in url: the page number, starts at 0 + +Output: +200 if success + +### Settings endpoints + +#### GET /api/setting + +Return user setting + +```json +{ + "alias_generator": "uuid", + "notification": true, + "random_alias_default_domain": "sl.local" +} +``` + +#### PATCH /api/setting + +Update user setting. All input fields are optional. + +Input: +- alias_generator (string): uuid or word +- notification (boolean): true or false +- random_alias_default_domain (string): one of the domains returned by `GET /api/setting/domains` + +Output: same as `GET /api/setting` + +#### GET /api/setting/domains + +Return domains that user can use to create random alias + +```json +[ + [ + true, + "d1.test" + ], + [ + true, + "d2.test" + ], + [ + true, + "sl.local" + ], + [ + false, + "ab.cd" + ] +] +``` + + +### Misc endpoints +#### POST /api/apple/process_payment + +Process payment receipt + +Input: +- `Authentication` in header: the api key +- `receipt_data` in body: the receipt_data base64Encoded returned by StoreKit, i.e. `rawReceiptData.base64EncodedString` +- (optional) `is_macapp` in body: if this field is present, the request is sent from the MacApp (Safari Extension) and not iOS app. + +Output: +200 if user is upgraded successfully +4** if any error. \ No newline at end of file diff --git a/docs/oauth.md b/docs/oauth.md new file mode 100644 index 00000000..5c6cb4bc --- /dev/null +++ b/docs/oauth.md @@ -0,0 +1,64 @@ +## OAuth + +SL currently supports code and implicit flow. + +#### Code flow + +To trigger the code flow locally, you can go to the following url after running `python server.py`: + +``` +http://localhost:7777/oauth/authorize?client_id=client-id&state=123456&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A7000%2Fcallback&state=random_string +``` + +You should see there the authorization page where user is asked for permission to share their data. Once user approves, user is redirected to this url with an `authorization code`: `http://localhost:7000/callback?state=123456&code=the_code` + +Next, exchange the code to get the token with `{code}` replaced by the code obtained in previous step. The `http` tool used here is https://httpie.org + +``` +http -f -a client-id:client-secret http://localhost:7777/oauth/token grant_type=authorization_code code={code} +``` + +This should return an `access token` that allows to get user info via the following command. Again, `http` tool is used. + +``` +http http://localhost:7777/oauth/user_info 'Authorization:Bearer {token}' +``` + +#### Implicit flow + +Similar to code flow, except for the the `access token` which we we get back with the redirection. +For implicit flow, the url is + +``` +http://localhost:7777/oauth/authorize?client_id=client-id&state=123456&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3A7000%2Fcallback&state=random_string +``` + +#### OpenID and OAuth2 response_type & scope + +According to the sharing web blog titled [Diagrams of All The OpenID Connect Flows](https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660), we should pay attention to: + +- `response_type` can be either `code, token, id_token` or any combination of those attributes. +- `scope` might contain `openid` + +Below are the potential combinations that are taken into account in SL until now: + +``` +response_type=code + scope: + with `openid` in scope, return `id_token` at /token: OK + without: OK + +response_type=token + scope: + with and without `openid`, nothing to do: OK + +response_type=id_token + return `id_token` in /authorization endpoint + +response_type=id_token token + return `id_token` in addition to `access_token` in /authorization endpoint + +response_type=id_token code + return `id_token` in addition to `authorization_code` in /authorization endpoint + +```