Initial commit
This commit is contained in:
70
docs/general/2fa.md
Normal file
70
docs/general/2fa.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Setting up 2FA
|
||||
|
||||
This document describes Barong [TOTP](https://tools.ietf.org/html/rfc6238) setup
|
||||
using [Vault](https://www.vaultproject.io/intro/getting-started/install.html).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
[Vault](https://www.vaultproject.io/intro/getting-started/install.html)
|
||||
with [TOTP secrets engine](https://www.vaultproject.io/docs/secrets/totp/index.html#setup) enabled.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use Vault with Barong you will need to set the following environment variables:
|
||||
|
||||
```shell
|
||||
export VAULT_ADDR=http://your-vault-url.com
|
||||
export VAULT_TOKEN=12345-vault-t0k3n-54321
|
||||
```
|
||||
|
||||
To allow using Google Authenticator `VAULT_ADDR` should be _public_ ip.
|
||||
|
||||
Note, that TOTP uses time-based algorithm.
|
||||
So, if you want to test 2FA with phone, make sure, that your Vault's server time and your phone's time are synchronized, or it will not work.
|
||||
[ntpdate](http://doc.ntp.org/4.1.1/ntpdate.htm) can help you to update your time with ntp servers:
|
||||
|
||||
```shell
|
||||
sudo ntpdate 0.ua.pool.ntp.org
|
||||
```
|
||||
|
||||
|
||||
## Developer How-tos
|
||||
|
||||
### Getting a code without Google Authenticator:
|
||||
|
||||
* From _shell_:
|
||||
|
||||
```shell
|
||||
$ vault login
|
||||
$ vault read totp/code/IDMYAWESOMEID
|
||||
```
|
||||
|
||||
* From _rails console_:
|
||||
|
||||
```ruby
|
||||
> me = Account.find_by_email('me@example.com')
|
||||
> Vault.logical.read("totp/code/#{me.uid}")
|
||||
```
|
||||
|
||||
### Getting a new key (e.g. if you lost your Google Authenticator):
|
||||
|
||||
* From _shell_:
|
||||
|
||||
```shell
|
||||
$ vault login
|
||||
$ vault write totp/keys/IDMYAWESOMEID \
|
||||
generate=true \
|
||||
issuer=Barong \
|
||||
account_name=me@example.com
|
||||
```
|
||||
|
||||
* From _rails console_:
|
||||
|
||||
```ruby
|
||||
> me = Account.find_by_email('me@example.com')
|
||||
> Vault::TOTP.send(:create, me.uid)
|
||||
```
|
||||
|
||||
Each response includes equivalent base64-encoded barcode and OTP url.
|
||||
You can find the key's secret in this OTP url query params.
|
||||
|
||||
35
docs/general/activities.md
Normal file
35
docs/general/activities.md
Normal file
@@ -0,0 +1,35 @@
|
||||
### Activities
|
||||
|
||||
To track admin activities you need to define it on seed.yml on `permissions` key
|
||||
- `role` should be in a range of existing: `admin`, `superadmin`, `support`, `techical`, `accountant`
|
||||
- `verb` should be `post`, `get`, `put`, `delete`
|
||||
- `path` - endpoint which should be checked, should be started with `api/v2/#{component}` as prefix
|
||||
- `action` should be `audit`
|
||||
```
|
||||
For example
|
||||
|
||||
permissions:
|
||||
- { role: 'admin', verb: 'post', path: api/v2/admin, action: audit }
|
||||
```
|
||||
|
||||
Here you can see a list of possible fields for activity:
|
||||
| Field | Type | Description |
|
||||
|:-----------|:--------:|:-----------:|
|
||||
| user_id | bigint | ID of user who creates activity |
|
||||
| target_uid | string | User UID for whom activity was created (admin remove OTP for user, target_uid will be uid of user for which admin removed OTP|
|
||||
| category | string | `admin` (admin activities), `user` (user activities)|
|
||||
| user_ip | string | IP address |
|
||||
| user_agent | string | User Agent such as `Mozilla/5.0`|
|
||||
| topic | string | Defined topic (`session`, `adjustments`) or `general` by default|
|
||||
| action | string | API action: `POST => 'create'`, `PUT => 'update'`, `GET => 'read'`, `DELETE => 'delete'`, `PATCH => 'update'` or `system` if there is no match of HTTP method|
|
||||
| result | string | Status of API response: `succeed`, `failed`, `denied`|
|
||||
| data | text | Parameters which was sent to specific API endpoint|
|
||||
| created_at | datetime | Time of activity creation|
|
||||
|
||||
##### Useful commands
|
||||
If you want to delete old activities you can run next command
|
||||
|
||||
Be sure that your parameters has valid date string, such as `YYYY-mm-dd` !
|
||||
```
|
||||
bundle exec rake activities:delete[from,to]
|
||||
```
|
||||
142
docs/general/api-keys.md
Normal file
142
docs/general/api-keys.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Barong API keys creation and usage
|
||||
|
||||
This document explains how to create an API key on barong using the UI or command line tool.
|
||||
This API key can be used to access microservices in the cluster protected by barong authentication.
|
||||
You can find below an example how to use the API key.
|
||||
|
||||
## How to create API key ?
|
||||
|
||||
### Using UI (recommended option)
|
||||
|
||||
1. Find API keys section (often located on profile page).
|
||||
|
||||

|
||||
|
||||
2. Create your API key and securely save Access Key and Secret Key
|
||||
|
||||

|
||||
|
||||
### Using API (use this option in case your frontend doesn't support API keys feature)
|
||||
|
||||
1. Install [httpie](https://httpie.org/)
|
||||
|
||||
2. Login into your account using httpie
|
||||
|
||||
```bash
|
||||
http --session barong_session https://your.domain/api/v2/barong/identity/sessions \
|
||||
email=your@email.com password=changeme otp_code=000000
|
||||
```
|
||||
Example of response:
|
||||
```json
|
||||
{
|
||||
"created_at": "2020-06-01T07:01:20Z",
|
||||
"csrf_token": "f5b36515a428328e199a",
|
||||
"data": "{\"language\":\"en\"}",
|
||||
"data_storages": [],
|
||||
"email": "your@example.com",
|
||||
"labels": [
|
||||
{
|
||||
"created_at": "2020-06-01T07:01:45Z",
|
||||
"key": "email",
|
||||
"scope": "private",
|
||||
"updated_at": "2020-06-01T07:01:45Z",
|
||||
"value": "verified"
|
||||
}
|
||||
],
|
||||
"level": 5,
|
||||
"otp": true,
|
||||
"phones": [
|
||||
{
|
||||
"country": "FR",
|
||||
"number": "33*****0471",
|
||||
"validated_at": "2020-06-01T07:03:18.000Z"
|
||||
}
|
||||
],
|
||||
"profiles": [],
|
||||
"referral_uid": null,
|
||||
"role": "member",
|
||||
"state": "active",
|
||||
"uid": "IDAF1AED1A42",
|
||||
"updated_at": "2020-10-22T18:01:09Z"
|
||||
}
|
||||
```
|
||||
|
||||
3. Validate your session
|
||||
|
||||
```bash
|
||||
http --session barong_session https://your.domain.com/api/v2/peatio/account/balances
|
||||
```
|
||||
|
||||
4. Create your API key
|
||||
|
||||
```
|
||||
http --session barong_session https://your.domain.com/api/v2/barong/resource/api_keys \
|
||||
algorithm=HS256 totp_code=681757 x-csrf-token:f5b36515a428328e199a
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{
|
||||
"algorithm": "HS256",
|
||||
"created_at": "2019-12-23T12:22:15Z",
|
||||
"kid": "61d025b8573501c2", // Access Key
|
||||
"scope": [],
|
||||
"secret": {
|
||||
"auth": null,
|
||||
"data": {
|
||||
"value": "2d0b4979c7fe6986daa8e21d1dc0644f" // Secret Key
|
||||
},
|
||||
"lease_duration": 2764800,
|
||||
"lease_id": "",
|
||||
"metadata": null,
|
||||
"renewable": false,
|
||||
"warnings": null,
|
||||
"wrap_info": null
|
||||
},
|
||||
"state": "active",
|
||||
"updated_at": "2019-12-23T12:22:15Z"
|
||||
}
|
||||
```
|
||||
|
||||
5. Securely save Access Key and Secret Key
|
||||
|
||||
## How to use API key ?
|
||||
|
||||
To authenticate using API key you need to pass next 3 headers:
|
||||
|
||||
| Header | Description |
|
||||
| ---------------- | ------------------------------------------------------------ |
|
||||
| X-Auth-Apikey | Access Key for API key (see 'How to create API key section ?') |
|
||||
| X-Auth-Nonce | Timestamp in milliseconds (can be passed as a string) |
|
||||
| X-Auth-Signature | HMAC-SHA256, calculated using concatenation of X-Auth-Nonce and Access Key |
|
||||
|
||||
1. Generate X-Auth-Nonce - unique string (e.g current unix timestamp)
|
||||
|
||||
```bash
|
||||
date +%s%3N
|
||||
1584524005143
|
||||
```
|
||||
|
||||
Nonce will be validated on server side to be not older than 5 seconds from the generation moment
|
||||
|
||||
2. Calculate X-Auth-Signature header.
|
||||
|
||||
X-Auth-Signature is HMAC-SHA256, calculated using concatenation of X-Auth-Nonce and Access Key.
|
||||
|
||||
```ruby
|
||||
nonce = (Time.now.to_f * 1000).to_i.to_s # timestamp in milliseconds, ex: 1584524005143
|
||||
access_key = '61d025b8573501c2' # Access Key from 'How to create API key section ?'
|
||||
secret_key = '2d0b4979c7fe6986daa8e21d1dc0644f' # Secret Key from 'How to create API key section ?'
|
||||
OpenSSL::HMAC.hexdigest("SHA256", secret_key, nonce + access_key)
|
||||
# => "bd42b945e095880e28d046846dbecf655fdf09d95a396a24fe6fe1df42f15d13"
|
||||
```
|
||||
|
||||
3. Pass your headers in httpie (note `--session` is not needed anymore)
|
||||
|
||||
```
|
||||
http https://your.domain.com/api/v2/peatio/account/balances \
|
||||
"X-Auth-Apikey: 61d025b8573501c2" \
|
||||
"X-Auth-Nonce: 1584524005143" \
|
||||
"X-Auth-Signature: bd42b945e095880e28d046846dbecf655fdf09d95a396a24fe6fe1df42f15d13"
|
||||
```
|
||||
48
docs/general/auth0.md
Normal file
48
docs/general/auth0.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Auth0 integration
|
||||
|
||||
## How to create an application
|
||||
|
||||
When you signed up for Auth0, a new application was created for you, or you could have created a new one (the most appropriate application for our structure is Single Page Application).
|
||||

|
||||
|
||||
You will need some details about that application to communicate with Auth0. You can get these details from the Application Settings section in the Auth0 dashboard.
|
||||
|
||||

|
||||
|
||||
You should put `auth0_domain` from the Domain field and `auth0_client_id` from the Client ID field
|
||||
|
||||
For a single page application better to use authorization code flow with proof key for code exchange.
|
||||
|
||||
## Authorization Code Flow with Proof Key for Code Exchange (PKCE)
|
||||
When public clients request Access Tokens, some additional security concerns are posed that are not mitigated by the Authorization Code Flow alone. This is because single-page apps cannot securely store a Client Secret because their entire source is available to the browser.
|
||||
|
||||
|
||||
### How it works
|
||||

|
||||
Because the PKCE-enhanced Authorization Code Flow builds upon the standard Authorization Code Flow, the steps are very similar.
|
||||
|
||||
1. The user clicks Login within the application.
|
||||
|
||||
2. Auth0's SDK creates a cryptographically-random code_verifier and from this generates a code_challenge.
|
||||
|
||||
3. Auth0's SDK redirects the user to the Auth0 Authorization Server (/authorize endpoint) along with the code_challenge.
|
||||
|
||||
4. Your Auth0 Authorization Server redirects the user to the login and authorization prompt.
|
||||
|
||||
5. The user authenticates using one of the configured login options and may see a consent page listing the permissions Auth0 will give to the application.
|
||||
|
||||
6. Your Auth0 Authorization Server stores the code_challenge and redirects the user back to the application with an authorization code, which is good for one use.
|
||||
|
||||
7. Auth0's SDK sends this code and the code_verifier (created in step 2) to the Auth0 Authorization Server (/oauth/token endpoint).
|
||||
|
||||
8. Your Auth0 Authorization Server verifies the code_challenge and code_verifier.
|
||||
|
||||
9. Your Auth0 Authorization Server responds with an ID Token and Access Token (and optionally, a Refresh Token).
|
||||
|
||||
10. Your application can use the Access Token to call an API to access information about the user.
|
||||
|
||||
11. The API responds with requested data.
|
||||
|
||||
You can try to call your [API using the authorization Code Flow with PKCE](https://auth0.com/docs/flows/call-your-api-using-the-authorization-code-flow-with-pkce).
|
||||
|
||||
Also you can find a link with [Authentication API description](https://auth0.com/docs/api/authentication#introduction) here.
|
||||
32
docs/general/captcha.md
Normal file
32
docs/general/captcha.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Barong Captcha Policy
|
||||
#### Overview
|
||||
A CAPTCHA (an acronym for "Completely Automated Public Turing test to tell Computers and Humans Apart") is a type of challenge–response test used in computing to determine whether or not the user is human) [Link to wiki](https://en.wikipedia.org/wiki/CAPTCHA)
|
||||
|
||||
Currently Barong versions 2.3+ supports 3 options in captcha policy on `sign up` and `sign in` API endpoints.
|
||||
|
||||
Configuration manages through environment variable - `BARONG_CAPTCHA`. Available values - `geetest`, `recaptcha`, `none`.
|
||||
With a wrong value barong will fail on start with error: `#{KEY} invalid, enabled values: NONE GEETEST RECAPTCHA`.
|
||||
|
||||
## Disabled (default)
|
||||
`none` - if ENV `BARONG_CAPTCHA` has this value - no captcha response will be required on sign in and sign up, so no bot traffic prevention.
|
||||
This option is not recommended to use in `production` environment.
|
||||
`None` policy was designed in testing and demo purposes, to start barong without any additional keys.
|
||||
|
||||
## Re CAPTCHA v2
|
||||
reCAPTCHA is a free service that protects your site from spam and abuse. It uses advanced risk analysis techniques to tell humans and bots apart. [Get started from google team](https://developers.google.com/recaptcha/intro)
|
||||
|
||||
`recaptcha` - this value in `BARONG_CAPTCHA` env enables re_captcha protection, designed and maintained by Google company. [Small developers tips from google team](https://developers.google.com/recaptcha/docs/display)
|
||||
|
||||
To properly configurate re_captcha you will need to set value for ENVs `recaptcha_site_key` and `recaptcha_secret_key`. Both of them you can generate [in google admin panel](https://www.google.com/recaptcha/admin/create)
|
||||
|
||||
After enabling and configuring captcha, `sign up` and `sign in` endpoint will require new parameter - `captcha_response`(`string`) and validate captcha response on server side, to protect from bots traffic.
|
||||
|
||||
## Geetest Captcha (Puzzle captcha)
|
||||
|
||||
GeeTest captcha is an user-friendly captcha with high security. GeeTest captcha enables digital businesses to secure control of their websites against bots. [geetest captcha site](https://www.geetest.com)
|
||||
|
||||
`geetest` - this value in BARONG_CAPTCHA env enables geetest captcha protection, designed and maintained by geetest.com
|
||||
|
||||
To properly configurate `geetest` you will need to set value for ENVs `geetest_id` and `geetest_key`. How to generate them, you can find in official [get started guide](https://docs.geetest.com/captcha/overview/guide)
|
||||
|
||||
After enabling and configuring geetest captcha, `sign up` and `sign in` endpoint will require new parameter - `captcha_response`(`hash` - with three keys `geetest_challenge`, `geetest_seccode`, `geetest_validate`) and validate captcha response on server side, to protect from bots traffic.
|
||||
46
docs/general/encryption.md
Normal file
46
docs/general/encryption.md
Normal file
@@ -0,0 +1,46 @@
|
||||
## Encryption
|
||||
|
||||
Data sensitivity will be defined as follow:\
|
||||
**Low**: IP address\
|
||||
**Medium**: Email address, Location data\
|
||||
**High**: Full name, Street address, phone number, date of birth\
|
||||
**Very High**: Passport number, Driver’s license number
|
||||
|
||||
Low and Medium will not be masked on the UI.
|
||||
|
||||
| Field | Mask | Comment |
|
||||
|---|---|---|
|
||||
| Street address | No mask | No need |
|
||||
| First Name | No mask | No need |
|
||||
| Last Name | B****** | Display first letter |
|
||||
| Phone Number | +380 **** 4556 | Display country code and last 4 digits |
|
||||
| Date of birth | 1980-01-** | Hide day |
|
||||
| Document Number | FG****64 | First 2 number and last 2 digits |
|
||||
|
||||
### Approach
|
||||
Rails offers a handy ActiveSupport::MessageEncryptor class, that hides away all the complexity of data encryption, and was wrapped in a simple to use service object or reusable module.
|
||||
|
||||
Service object class doing the actual heavy lifting, but only exposing two straightforward public class methods encrypt and decrypt.
|
||||
|
||||
System have weekly salt rotation, so encrypted keys in DB will be prepended with salt, which will be mix of year and week number starting from 0.
|
||||
|
||||
Make sure to store `SECRET_KEY_BASE` somewhere safe otherwise, you would not be able to decrypt your secure data, also you need to have this ENV variable at the start of your application as it will not create models (Profile, Phone, Documents) which have encrypted fields.
|
||||
|
||||
#### Searching by encrypted values
|
||||
|
||||
To have ability to search by encrypted fields, system implements additional field named `attribute_index` which use [crc32 algorithm](http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html) for storing attribute value.
|
||||
|
||||
Make sure you have `BARONG_CRC32_SALT` to make algrithm more powerful.
|
||||
|
||||
#### Rotation
|
||||
|
||||
To update all encrypted fields to latest key values (salt will be "#{current_year}#{current_week}"), you can use following rake tasks:
|
||||
|
||||
`rake rotate:phones`
|
||||
`rake rotate:profiles`
|
||||
`rake rotate:documents`
|
||||
|
||||
### Fields masking on user API
|
||||
|
||||
Sensitive data fields like `last name`, `dob`, `phone number`, `document number` are masked in user API by default.
|
||||
You can disable this masking by changing the environment variable `BARONG_API_DATA_MASKING_ENABLED` to `false`.
|
||||
179
docs/general/errors.md
Normal file
179
docs/general/errors.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Barong errors list
|
||||
|
||||
## Resource module errors
|
||||
|
||||
```
|
||||
resource.labels.private - Can't update Label.
|
||||
resource.user.no_activity No activity recorded or wrong topic
|
||||
resource.user.empty_otp_code Cant delete account. 2FA is on, but otp_code is empty
|
||||
resource.user.invalid_otp Cant delete account. 2FA is on, but otp_code is invalid
|
||||
resource.user.missing_otp_code Cant delete account. 2FA is on, but otp_code is missing
|
||||
resource.user.invalid_password Cant delete account. Password is wrong
|
||||
resource.profile.not_exist User has no profile
|
||||
resource.profile.exist Profile already exists
|
||||
resource.api_key.2fa_disabled Only accounts with enabled 2FA alowed
|
||||
resource.api_key.missing_otp Theaccount has enabled 2FA but OTP code is missing
|
||||
resource.api_key.invalid_otp OTP code is invalid
|
||||
resource.phone.twillio Something wrong with Twilio Client
|
||||
resource.phone.invalid_num Phone number is invalid
|
||||
resource.phone.number_exist Phone number already exists
|
||||
resource.phone.verification_invalid Phone is not found or verification code is invalid
|
||||
resource.documents.limit_reached Maximum number of documents already reached
|
||||
resource.documents.limit_will_be_reached Documents amount will reach limit by this upload
|
||||
resource.otp.already_enabled 2FA has been already enabled for this account
|
||||
resource.otp.invalid OTP code is invalid
|
||||
resource.password.doesnt_match New passwords don\'t match
|
||||
resource.password.prev_pass_not_correct Previous password is not correct
|
||||
resource.password.no_change_provided New password cant be the same, as old one
|
||||
```
|
||||
|
||||
## Identity module errors
|
||||
|
||||
```
|
||||
identity.user.invalid_referral_format Invalid referral uid format
|
||||
identity.user.referral_doesnt_exist Referral doesn't exist
|
||||
identity.user.active_or_doesnt_exist User doesn't exist or has already been activated'
|
||||
identity.password.user_doesnt_exist User doesn't exist
|
||||
identity.user.passwords_doesnt_match Passwords don't match
|
||||
identity.user.utilized_token JWT has already been used
|
||||
identity.session.invalid_params Invalid Email or Password
|
||||
identity.session.invalid Invalid Session
|
||||
identity.captcha.required captcha_response is required
|
||||
identity.captcha.mandatory_fields Mandatory fields must be filled in
|
||||
identity.session.deleted Your account is deleted
|
||||
identity.session.not_active Your account is not active
|
||||
identity.session.banned Your account is banned
|
||||
identity.session.invalid_params Invalid Email or Password
|
||||
identity.session.missing_otp The account has enabled 2FA but OTP code is missing
|
||||
identity.session.invalid_otp OTP code is invalid
|
||||
```
|
||||
|
||||
## Admin module errors
|
||||
|
||||
```
|
||||
admin.user.update_himself Admin can't update himself
|
||||
admin.user.enable_2fa Manual 2FA enabling not allowed
|
||||
admin.user.state_no_change Can't change state, as its already {active}
|
||||
admin.user.doesnt_exist User with such UID doesnt exist
|
||||
admin.label.doesnt_exist Label with such key doesnt exist or not assigned to chosen user
|
||||
admin.access.denied Access Denied: User is not Admin
|
||||
admin.user.non_user_field Search field is not a user attribute
|
||||
admin.user.no_matches Search result is empty array
|
||||
admin.user.label_no_matches Search result is empty array
|
||||
```
|
||||
|
||||
## General errors
|
||||
|
||||
```
|
||||
record.not_found Record is not found
|
||||
jwt.decode_and_verify Failed to decode and verify JWT
|
||||
authz.invalid_session Failed to decode cookies
|
||||
authz.user_not_active User is not active
|
||||
authz.invalid_signature API Key header 'signature' is invalid
|
||||
authz.apikey_not_active API Key state is 'inactive'
|
||||
authz.disabled_2fa API Key owner has disabled 2FA
|
||||
authz.invalid_api_key_headers Blank or missing API Key headers
|
||||
authz.permission_denied Path is blacklisted
|
||||
authz.unexistent_apikey X-Auth-Apikey header is invalid
|
||||
```
|
||||
|
||||
## Validation errors
|
||||
### Admin module
|
||||
|
||||
```
|
||||
admin.user.non_integer_page
|
||||
admin.user.non_positive_page
|
||||
admin.user.non_integer_limit
|
||||
admin.user.invalid_limit
|
||||
admin.user.missing_uid
|
||||
admin.user.empty_uid
|
||||
admin.user.empty_state
|
||||
admin.user.empty_otp
|
||||
admin.user.empty_role
|
||||
admin.user.one_of_state_otp
|
||||
admin.user.one_of_state_otp_email
|
||||
admin.user.missing_key
|
||||
admin.user.empty_key
|
||||
admin.user.missing_scope
|
||||
admin.user.empty_scope
|
||||
admin.user.missing_value
|
||||
admin.user.empty_value
|
||||
```
|
||||
|
||||
### Identity module
|
||||
|
||||
```
|
||||
identity.user.missing_email
|
||||
identity.user.empty_email
|
||||
identity.user.missing_password
|
||||
identity.user.empty_password
|
||||
identity.user.missing_token
|
||||
identity.user.empty_token
|
||||
identity.user.missing_reset_password_token
|
||||
identity.user.empty_reset_password_token
|
||||
identity.user.missing_confirm_password
|
||||
identity.user.empty_confirm_password
|
||||
|
||||
identity.session.missing_email
|
||||
identity.session.missing_password
|
||||
identity.session.invalid_captcha_format
|
||||
```
|
||||
|
||||
### Resource module
|
||||
|
||||
```
|
||||
resource.otp.missing_code
|
||||
resource.otp.empty_code
|
||||
|
||||
resource.labels.missing_key
|
||||
resource.labels.empty_key
|
||||
resource.labels.missing_value
|
||||
resource.labels.empty_value
|
||||
|
||||
resource.documents.expire_not_a_date
|
||||
resource.documents.invalid_format
|
||||
resource.documents.already_expired
|
||||
resource.documents.missing_doc_expire
|
||||
resource.documents.empty_doc_expire
|
||||
resource.documents.missing_doc_type
|
||||
resource.documents.empty_doc_type
|
||||
resource.documents.missing_doc_number
|
||||
resource.documents.empty_doc_number
|
||||
resource.documents.missing_upload
|
||||
|
||||
|
||||
resource.user.missing_topic
|
||||
resource.user.empty_topic
|
||||
resource.user.missing_old_password
|
||||
resource.user.empty_old_password
|
||||
resource.user.missing_new_password
|
||||
resource.user.empty_new_password
|
||||
resource.user.missing_confirm_password
|
||||
resource.user.empty_confirm_password
|
||||
|
||||
|
||||
resource.profile.missing_first_name
|
||||
resource.profile.missing_last_name
|
||||
resource.profile.missing_dob
|
||||
resource.profile.missing_address
|
||||
resource.profile.missing_postcode
|
||||
resource.profile.missing_city
|
||||
resource.profile.missing_country
|
||||
|
||||
|
||||
resource.api_key.missing_algorithm
|
||||
resource.api_key.empty_algorithm
|
||||
resource.api_key.empty_kid
|
||||
resource.api_key.empty_scope
|
||||
resource.api_key.missing_totp
|
||||
resource.api_key.empty_totp
|
||||
resource.api_key.missing_kid
|
||||
resource.api_key.empty_state
|
||||
|
||||
|
||||
|
||||
resource.phone.missing_phone_number
|
||||
resource.phone.empty_phone_number
|
||||
resource.phone.missing_verification_code
|
||||
resource.phone.empty_verification_code
|
||||
```
|
||||
593
docs/general/event_api.md
Normal file
593
docs/general/event_api.md
Normal file
@@ -0,0 +1,593 @@
|
||||
# RabbitMQ Barong Event API
|
||||
|
||||
## Overview of RabbitMQ details
|
||||
|
||||
Barong submits all events into three exchanges depending on event category (read next).
|
||||
|
||||
The exchange name consists of three parts:
|
||||
|
||||
1) application name (typically `barong`)
|
||||
|
||||
2) fixed keyword `events`.
|
||||
|
||||
3) category of event, like `system` (generic system event), `model` (the attributes of some record were updated)
|
||||
|
||||
The routing key looks like `user.password.reset.token`, `user.created`.
|
||||
The event name matches the routing key but with event category appended at the beginning, like `system.user.password.reset.token`, `market.user.created`.
|
||||
|
||||
## Overview of RabbitMQ message
|
||||
|
||||
Each produced message in `Event API` is JWT (complete format).
|
||||
|
||||
This is very similar to `Management API`.
|
||||
|
||||
The example below demonstrates both generation and verification of JWT:
|
||||
|
||||
```ruby
|
||||
require "jwt-multisig"
|
||||
require "securerandom"
|
||||
|
||||
jwt_payload = {
|
||||
iss: 'barong',
|
||||
jti: SecureRandom.uuid,
|
||||
iat: Time.now.to_i,
|
||||
exp: Time.now.to_i + 60,
|
||||
event: event_payload
|
||||
}
|
||||
|
||||
private_key = OpenSSL::PKey.read(Base64.urlsafe_decode64(private_key)
|
||||
algorithm = 'RS256'
|
||||
jwt = JWT::Multisig.generate_jwt jwt_payload, \
|
||||
{ barong: private_key },
|
||||
{ barong: algorithm }
|
||||
|
||||
Kernel.puts "GENERATED JWT", jwt.to_json, "\n"
|
||||
|
||||
verification_result = JWT::Multisig.verify_jwt jwt.deep_stringify_keys, \
|
||||
{ barong: public_key }, { verify_iss: true, iss: "barong", verify_jti: true }
|
||||
|
||||
decoded_jwt_payload = verification_result[:payload]
|
||||
|
||||
Kernel.puts "MATCH AFTER VERIFICATION: #{jwt_payload == decoded_jwt_payload}."
|
||||
```
|
||||
|
||||
The RabbitMQ message is stored in JWT field called `event`.
|
||||
|
||||
## Overview of Event API message
|
||||
|
||||
The typical event looks like (JSON):
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
foo: "ID30DD0DD986",
|
||||
bar: "example@barong.io",
|
||||
baz: "member",
|
||||
qux: 0
|
||||
},
|
||||
name: "model.user.created"
|
||||
}
|
||||
```
|
||||
|
||||
The field `event[:name]` contains event name (same as routing key).
|
||||
The fields `foo`, `bar`, `baz`, `qux` (example) are fields which carry useful information.
|
||||
|
||||
# Barong Event API messages
|
||||
|
||||
## Format of `model.user.created` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
},
|
||||
name: "model.user.created"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `record` | Created user up-to-date attributes. |
|
||||
|
||||
## Format of `model.user.updated` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 1,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
},
|
||||
changes: {
|
||||
level: 0
|
||||
}
|
||||
name: "model.user.updated"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `record` | Created user up-to-date attributes. |
|
||||
| `changes` | The changed user attributes and their values. |
|
||||
|
||||
|
||||
## Format of `model.user.created` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
address:"Illinois",
|
||||
city:"New Garfieldbury",
|
||||
country:"COD",
|
||||
dob:"1984-05-22",
|
||||
first_name:"Irina",
|
||||
last_name:"Heathcote",
|
||||
postcode:"10029",
|
||||
created_at:"2019-10-02T08:14:20Z",
|
||||
updated_at:"2019-10-02T08:14:20Z",
|
||||
user: {
|
||||
email: "clarisa_larkin@sawayn.info",
|
||||
level: 0,
|
||||
otp: false,
|
||||
referral_uid: nil,
|
||||
role: "member",
|
||||
state: "pending",
|
||||
uid: "IDEA819FB3F1",
|
||||
updated_at: "2019-10-02T08:14:20Z"
|
||||
created_at: "2019-10-02T08:14:20Z"
|
||||
}
|
||||
},
|
||||
name: "model.profile.created"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | -------------------------------------- |
|
||||
| `record` | Created profile up-to-date attributes. |
|
||||
|
||||
## Format of `model.user.updated` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
address:"Illinois",
|
||||
city:"New Garfieldbury",
|
||||
country:"COD",
|
||||
created_at:"2019-10-02T08:14:20Z",
|
||||
dob:"1984-05-22",
|
||||
first_name:"Irina",
|
||||
last_name:"Heathcote",
|
||||
postcode:"10029",
|
||||
updated_at:"2019-10-02T08:14:20Z",
|
||||
user: {
|
||||
email: "clarisa_larkin@sawayn.info",
|
||||
level: 0,
|
||||
otp: false,
|
||||
referral_uid: nil,
|
||||
role: "member",
|
||||
state: "pending",
|
||||
uid: "IDEA819FB3F1",
|
||||
updated_at: "2019-10-02T08:14:20Z"
|
||||
created_at: "2019-10-02T08:14:20Z"
|
||||
}
|
||||
},
|
||||
changes: {
|
||||
first_name: "Vernell"
|
||||
},
|
||||
name: "model.profile.updated"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `record` | Profile up-to-date attributes. |
|
||||
| `changes` | The changed profile attributes and their values. |
|
||||
|
||||
## Format of `model.label.created` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
id: 1,
|
||||
key: "email",
|
||||
value: "verified",
|
||||
user: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 2,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
}
|
||||
},
|
||||
name: "model.label.created"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `record` | Created label up-to-date attributes. |
|
||||
|
||||
## Format of `model.label.updated` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
id: 1,
|
||||
key: "new_key",
|
||||
value: "verified",
|
||||
user: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 2,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
}
|
||||
},
|
||||
changes: {
|
||||
key: "old_key"
|
||||
}
|
||||
name: "model.label.updated"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `record` | Created label up-to-date attributes. |
|
||||
| `changes` | The changed label attributes and their values. |
|
||||
|
||||
## Format of `model.document.created` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
doc_type: 'Passport',
|
||||
doc_expire: '3020-01-22',
|
||||
doc_number: 'AA1234BB',
|
||||
upload: [],
|
||||
updated_at:"2019-01-28T08:35:29Z",
|
||||
created_at:"2019-01-28T08:35:29ZZ",
|
||||
user: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 2,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
}
|
||||
}
|
||||
name: "model.document.created"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ------------ | --------------------------------- |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
| `doc_type` | Document type. |
|
||||
| `doc_expire` | Experation time for document. |
|
||||
| `doc_number` | Document number. |
|
||||
| `upload` | Array of updaded objects |
|
||||
| `updated_at` | Time of document object creation |
|
||||
| `created_at` | Time of last document update |
|
||||
|
||||
## Format of `system.user.email.confirmation.token` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "ID739065AFD3",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T09:03:50Z",
|
||||
updated_at: "2019-01-28T09:03:50Z"
|
||||
},
|
||||
language: "EN",
|
||||
domain: "www.barong.io",
|
||||
token: "eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NDg2NjYyMzAsImV4cCI6MTU0ODY3MjIzMCwic3ViIjoiY29uZmlybWF0aW9uIiwiaXNzIjoiYmFyb25nIiwiYXVkIjpbInBlYXRpbyIsImJhcm9uZyJdLCJqdGkiOiI5OWJkNzFkMjU2NTdlMmI1YzI1MCIsImVtYWlsIjoiYWRtaW4xMjNAYmFyb25nLmlvIiwidWlkIjoiSUQ3MzkwNjVBRkQzIn0.OI5tL9kV6cA1JBAy7G5iqd3WplxcB-waHYKFjm83koMEpx2Hlw9fksq5lip5cIHTjR8i3ambFL40OaCwDNc1jAiDsHwuv2nLswgi88_M1G8KVFylboQdtgmH_cZiz-Y-51Fq2oqEID5QyJnsSMSJbfspb6A0JGT_V-SPK4WFZw43F_RKhlZBCrxojljMwd20rGqFPYirMgUpsfiW0_-mESXzQ7UK1eA8mYO7Id4y6JR2Yoo-JTloEnBL1M189tOz6LqmmQB0M_QjTiHG3y9I97Med3StgVziYo9qog9kJXyPuXbboddg__5WEhMcWbaToohoiT5UvpVJHKfgxEVaDg"
|
||||
},
|
||||
name: "system.user.email.confirmation.token"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ------------------------------------------------ |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
| `language` | The language. |
|
||||
| `domain` | The domain name of barong. |
|
||||
| `token` | Valid confirm-acc jwt token (mandatory param for user confirmation endpoint) `/identity/users/email/confirm_code`. |
|
||||
|
||||
## Format of `system.user.email.confirmed` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "IDB1629BFE9E",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-28T10:17:27Z",
|
||||
updated_at: "2019-01-28T10:17:45Z"
|
||||
},
|
||||
language: "EN",
|
||||
domain: "www.barong.io"
|
||||
},
|
||||
name: "system.user.email.confirmed"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | -------------------------------- |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
| `language` | The language. |
|
||||
| `domain` | The domain name of barong. |
|
||||
|
||||
## Format of `system.user.password.reset.token` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
},
|
||||
language: "EN",
|
||||
domain: "www.barong.io",
|
||||
token: "eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NDg2NjQ1OTUsImV4cCI6MTU0ODY3MDU5NSwic3ViIjoicmVzZXQiLCJpc3MiOiJiYXJvbmciLCJhdWQiOlsicGVhdGlvIiwiYmFyb25nIl0sImp0aSI6IjRhY2IzM2IzYmE2NDc0ZjY1YTI5IiwiZW1haWwiOiJhZG1pbjEyQGJhcm9uZy5pbyIsInVpZCI6IklEMzBERDBERDk4NiJ9.Rie4LCbkV0jVBbhMoceYx8a9uDA-ea9D1v790zlIqP_EY8Iue_OOKXYWiC1Y-55MPicFbknBILjZlPewvAF8ZrhqIt04ROsgBdDGEUGY_SnLWhXzqSx9-v_o_w2MVjLOUxvRBm6sD0RvL-_5LmOcLqhYtf7ZPUnPDwsvhDedqDfbXPEvI7OK2SZ-1uPAOg1IMOX1k7xaDt5I1Wp-Knr2DmEgwNYbIjaXraComYcMdtVSuYVJAufgA0kTADMeT3cV3jzGy9dNfs8heMCtf5tr72IbL0_N0VeUQj9uaPDUr4ntsYk7gOPmA3RSVrSismtYdBXA9oLA0b0YfOctiY9dqg"
|
||||
},
|
||||
name: "system.user.password.reset.token"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ------------------------------------------------ |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
| `language` | The language. |
|
||||
| `domain` | The domain name of barong. |
|
||||
| `token` | Valid reset-pass jwt token (mandatory param for password reset endpoint) `/identity/users/password/confirm_code`. |
|
||||
|
||||
## Format of `system.user.account.deleted` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "IDB1629BFE9E",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 1,
|
||||
otp: false,
|
||||
state: "deleted",
|
||||
created_at: "2019-01-28T10:17:27Z",
|
||||
updated_at: "2019-01-28T10:17:45Z",
|
||||
}
|
||||
},
|
||||
name: "system.user.account.deleted"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ------------------------------------------------ |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
|
||||
## Format of `system.user.password.reset` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T09:42:36Z"
|
||||
}
|
||||
},
|
||||
name: "system.user.password.reset"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | ------------------------------------------------ |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
|
||||
## Format of `system.user.password.change` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "IDC554ED1D0F",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-09T15:54:56Z",
|
||||
updated_at: "2019-01-28T09:59:03Z"
|
||||
}
|
||||
},
|
||||
name: "system.user.password.change"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| --------- | ------------------------------------------------- |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
|
||||
## Format of `system.document.verified` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "IDC554ED1D0F",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-09T15:54:56Z",
|
||||
updated_at: "2019-01-28T09:59:03Z"
|
||||
},
|
||||
id: 1,
|
||||
key: "something",
|
||||
value: "verified"
|
||||
},
|
||||
name: "system.document.verified"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| --------- | ------------------------------------------------- |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
|
||||
## Format of `system.document.rejected` event
|
||||
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "IDC554ED1D0F",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "active",
|
||||
created_at: "2019-01-09T15:54:56Z",
|
||||
updated_at: "2019-01-28T09:59:03Z"
|
||||
},
|
||||
id: 1,
|
||||
key: "something",
|
||||
value: "rejected"
|
||||
},
|
||||
name: "system.document.rejected"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| --------- | ------------------------------------------------- |
|
||||
| `user` | The up-to-date user attributes. |
|
||||
|
||||
## Format of `system.session.create` event
|
||||
```ruby
|
||||
event: {
|
||||
record: {
|
||||
user: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
},
|
||||
user_ip: "127.0.0.1",
|
||||
user_agent: "Chrome"
|
||||
},
|
||||
name: "system.session.create"
|
||||
}
|
||||
```
|
||||
|
||||
## Producing events using Ruby
|
||||
|
||||
```ruby
|
||||
require "bunny"
|
||||
|
||||
def generate_jwt(jwt_payload)
|
||||
Kernel.abort "Please, see «Overview of RabbitMQ message» for implementation guide."
|
||||
end
|
||||
|
||||
Bunny.run host: "localhost", port: 5672, username: "guest", password: "guest" do |session|
|
||||
channel = session.channel
|
||||
exchange = channel.direct("barong.events.model")
|
||||
jwt_payload = {
|
||||
iss: "barong",
|
||||
jti: SecureRandom.uuid,
|
||||
iat: Time.now.to_i,
|
||||
exp: Time.now.to_i + 60,
|
||||
event: {
|
||||
record: {
|
||||
uid: "ID30DD0DD986",
|
||||
email: "example@barong.io",
|
||||
role: "member",
|
||||
level: 0,
|
||||
otp: false,
|
||||
state: "pending",
|
||||
created_at: "2019-01-28T08:35:29Z",
|
||||
updated_at: "2019-01-28T08:35:29Z"
|
||||
},
|
||||
name: "model.user.created"
|
||||
}
|
||||
}
|
||||
exchange.publish(generate_jwt(jwt_payload), routing_key: "user.created")
|
||||
end
|
||||
```
|
||||
|
||||
IMPORTANT: Don't forget to implement the logic for JWT exception handling!
|
||||
|
||||
## Producing events using `rabbitmqadmin`
|
||||
|
||||
`rabbitmqadmin publish routing_key=user.created payload=JWT exchange=barong.events.model`
|
||||
|
||||
Don't forget to pass environment variable `JWT`.
|
||||
|
||||
## Consuming events using Ruby
|
||||
|
||||
```ruby
|
||||
require "bunny"
|
||||
|
||||
def verify_jwt(jwt_payload)
|
||||
Kernel.abort "Please, see «Overview of RabbitMQ message» for implementation guide."
|
||||
end
|
||||
|
||||
Bunny.run host: "localhost", port: 5672, username: "guest", password: "guest" do |session|
|
||||
channel = session.channel
|
||||
exchange = channel.direct("barong.events.model")
|
||||
queue = channel.queue("", auto_delete: true, durable: true, exclusive: true)
|
||||
.bind(exchange, routing_key: "user.created")
|
||||
queue.subscribe manual_ack: true, block: true do |delivery_info, metadata, payload|
|
||||
Kernel.puts verify_jwt(JSON.parse(payload)).fetch(:event)
|
||||
channel.ack(delivery_info.delivery_tag)
|
||||
rescue => e
|
||||
channel.nack(delivery_info.delivery_tag, false, true)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
IMPORTANT: Don't forget to implement the logic for JWT exception handling!
|
||||
71
docs/general/password_hashing.md
Normal file
71
docs/general/password_hashing.md
Normal file
@@ -0,0 +1,71 @@
|
||||
## Barong password hashing ##
|
||||
|
||||
### Overview ###
|
||||
|
||||
Barong since 2.0 version use OpenBSD bcrypt() password hashing algorithm, that allow us easily store a secure hash of users' passwords.
|
||||
|
||||
As a base Barong takes [bcrypt-ruby gem](https://github.com/codahale/bcrypt-ruby) - Ruby binding for the OpenBSD bcrypt()
|
||||
With [rails 5 has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) it gives us full power of algorithm
|
||||
|
||||
### How it works ###
|
||||
|
||||
Hash algorithms take a chunk of data (e.g., user's password) and create a "digital fingerprint," or hash, of it.
|
||||
Because this process is not reversible, there's no way to go from the hash back to the password.
|
||||
|
||||
In other words:
|
||||
|
||||
hash(p) #=> <unique gibberish>
|
||||
|
||||
We store the hash and check it against a hash made of a potentially valid password:
|
||||
|
||||
<unique gibberish> =? hash(just_entered_password)
|
||||
|
||||
### Rainbow Tables
|
||||
|
||||
But even this has weaknesses -- attackers can just run lists of possible passwords through the same algorithm, store the
|
||||
results in a big database, and then look up the passwords by their hash:
|
||||
|
||||
PrecomputedPassword.find_by_hash(<unique gibberish>).password #=> "secret1"
|
||||
|
||||
Our solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed:
|
||||
|
||||
hash(salt + p) #=> <really unique gibberish>
|
||||
|
||||
The salt is then stored along with the hash in the database, and used to check potentially valid passwords:
|
||||
|
||||
<really unique gibberish> =? hash(salt + just_entered_password)
|
||||
|
||||
bcrypt-ruby automatically handles the storage and generation of these salts for you.
|
||||
|
||||
Adding a salt means that an attacker has to have a gigantic database for each unique salt -- for a salt made of 4
|
||||
letters, that's 456,976 different databases. Pretty much no one has that much storage space, so attackers try a
|
||||
different, slower method -- throw a list of potential passwords at each individual password:
|
||||
|
||||
hash(salt + "aadvark") =? <really unique gibberish>
|
||||
hash(salt + "abacus") =? <really unique gibberish>
|
||||
etc.
|
||||
|
||||
This is much slower than the big database approach, but most hash algorithms are pretty quick -- and therein lies the
|
||||
problem. Hash algorithms aren't usually designed to be slow, they're designed to turn gigabytes of data into secure
|
||||
fingerprints as quickly as possible. `bcrypt()`, though, is designed to be computationally expensive:
|
||||
|
||||
Ten thousand iterations:
|
||||
user system total real
|
||||
md5 0.070000 0.000000 0.070000 ( 0.070415)
|
||||
bcrypt 22.230000 0.080000 22.310000 ( 22.493822)
|
||||
|
||||
If an attacker was using Ruby to check each password, they could check ~140,000 passwords a second with MD5 but only
|
||||
~450 passwords a second with `bcrypt()`.
|
||||
|
||||
## More Information
|
||||
|
||||
`bcrypt()` is currently used as the default password storage hash in OpenBSD, widely regarded as the most secure operating
|
||||
system available.
|
||||
|
||||
For a more technical explanation of the algorithm and its design criteria, please read Niels Provos and David Mazières'
|
||||
Usenix99 paper:
|
||||
https://www.usenix.org/events/usenix99/provos.html
|
||||
|
||||
If you'd like more down-to-earth advice regarding cryptography, I suggest reading <i>Practical Cryptography</i> by Niels
|
||||
Ferguson and Bruce Schneier:
|
||||
https://www.schneier.com/book-practical.html
|
||||
45
docs/general/profiles.md
Normal file
45
docs/general/profiles.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Barong
|
||||
|
||||
## Profiles story and administration
|
||||
|
||||
This document explain original profiles submit-n-verify process and possible customizations.
|
||||
|
||||
## Version
|
||||
|
||||
Story described in the document actual for latest 2.5 stable version and higher.
|
||||
|
||||
## User side of the story
|
||||
|
||||
`Comment`: Previously (in 2.3 and lower) user was able to submit only 1 profile, and all later modifications affect it. Starting from 2.4 we changed `user has_one profile` relation to `user has many profiles`. This was done first of all to be able to track history of modifications and to be able to control changes from admin panel. Meanwhile, it also brought additional manual verification step in the legacy KYC process.
|
||||
|
||||
`Story`:
|
||||
User can submit profile with following fields (all are optional by default): (via `POST /resource/profiles`)
|
||||
|
||||
```
|
||||
t.string "first_name"
|
||||
t.string "last_name"
|
||||
t.date "dob"
|
||||
t.string "address"
|
||||
t.string "postcode"
|
||||
t.string "city"
|
||||
t.string "country"
|
||||
t.text "metadata"
|
||||
```
|
||||
|
||||
Profile creates with `drafted` state in database. At this point user can edit the information (via `PUT /resource/profiles`), and administrators will not review it yet.
|
||||
Once all the information is edited and validated by user correctly, he can submit profile for verification (via `PUT /resource/profiles`) by passing `confirmation: true` in the params. Profile state changes to "submitted" in database and from now on this profile is pending for admin verification.
|
||||
|
||||
Meanwhile, there is a possibility to skip this "edit" step and create a profile directly with `submitted` state. For this user need to pass `confirmation: true` parameter directly in `POST /resource/profiles`.
|
||||
|
||||
After admin will verify the profile and mark it as `"verified"` or `"rejected"` user will be able to create a new profile with `drafted/submitted` state, if he need it. Flow mostly controls by a `server-side rule`: user can have `ANY` amount of profiles, but `ONLY ONE` of `drafted/submitted` at a time.
|
||||
|
||||
## Admin side of the story
|
||||
|
||||
Once user sumbit a profile (with state submitted) admin can verify or reject it, by changing a profile state and creating a correct label. Usually its `key: profile, value: verified/rejected'.
|
||||
|
||||
Also administrator has an access to the full profiles history, so he can check and compare new changes with old profiles, if the exist. As well he has an information about previous decisions about profile verification per each request.
|
||||
|
||||
Once profile is rejected or verified, admin can create new profile for user (via `POST admin/profiles`).
|
||||
In this case, profile will have a 'submitted' state and 'author' field with admin UID in DB.
|
||||
|
||||
`!!!Attention` By default, if admin creates a profile for user, the same admin account cant approve or reject this profile, he need to wait for second admin approval. However, this can be changed by env `BARONG_PROFILE_DOUBLE_VERIFICATION` which can receive 2 value: `true` for enabling and `false` for disabling the feature
|
||||
Reference in New Issue
Block a user