├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── authman-scm-1.rockspec ├── authman ├── db.lua ├── error.lua ├── init.lua ├── migrations │ └── migrations.lua ├── model │ ├── oauth │ │ ├── app.lua │ │ ├── code.lua │ │ ├── consumer.lua │ │ ├── consumer │ │ │ ├── redirect.lua │ │ │ └── scope.lua │ │ └── token.lua │ ├── password.lua │ ├── password_token.lua │ ├── session.lua │ ├── social.lua │ └── user.lua ├── oauth │ └── oauth.lua ├── response.lua ├── utils │ ├── http.lua │ └── utils.lua └── validator.lua ├── config └── config.default.lua ├── debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── rules ├── source │ └── format └── tarantool-authman.install ├── doc └── oauth2.md ├── rpm └── tarantool-authman.spec └── test ├── authman.test.lua ├── case ├── auth.lua ├── complex.lua ├── oauth │ ├── app.lua │ ├── oauth.lua │ ├── redirect.lua │ └── scope.lua ├── profile.lua ├── registration.lua ├── restore_password.lua └── social.lua ├── config.lua ├── mock └── authman │ └── utils │ └── http.lua └── values.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.snap 3 | *.vymeta 4 | *.xlog 5 | t.lua 6 | config/config.lua 7 | tarantool.log 8 | packpack 9 | old-tarantool-auth.spec 10 | build 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: C 3 | services: 4 | - docker 5 | 6 | cache: 7 | directories: 8 | - $HOME/.cache 9 | 10 | git: 11 | depth: 100500 12 | 13 | env: 14 | global: 15 | - PRODUCT=tarantool-authman 16 | matrix: 17 | - OS=el DIST=6 18 | - OS=el DIST=7 19 | - OS=fedora DIST=24 20 | - OS=fedora DIST=25 21 | - OS=ubuntu DIST=precise 22 | - OS=ubuntu DIST=trusty 23 | - OS=ubuntu DIST=xenial 24 | - OS=debian DIST=wheezy 25 | - OS=debian DIST=jessie 26 | - OS=debian DIST=stretch 27 | 28 | script: 29 | - git describe --long 30 | - git clone https://github.com/packpack/packpack.git packpack 31 | - packpack/packpack 32 | 33 | before_deploy: 34 | - ls -l build/ 35 | 36 | deploy: 37 | # Deploy packages to PackageCloud 38 | - provider: packagecloud 39 | username: tarantool 40 | repository: "1_7" 41 | token: ${PACKAGECLOUD_TOKEN} 42 | dist: ${OS}/${DIST} 43 | package_glob: build/*.{deb,rpm,dsc} 44 | skip_cleanup: true 45 | on: 46 | branch: master 47 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 48 | - provider: packagecloud 49 | username: tarantool 50 | repository: "1_9" 51 | token: ${PACKAGECLOUD_TOKEN} 52 | dist: ${OS}/${DIST} 53 | package_glob: build/*.{deb,rpm,dsc} 54 | skip_cleanup: true 55 | on: 56 | branch: master 57 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 58 | - provider: packagecloud 59 | username: tarantool 60 | repository: "2_0" 61 | token: ${PACKAGECLOUD_TOKEN} 62 | dist: ${OS}/${DIST} 63 | package_glob: build/*.{deb,rpm,dsc} 64 | skip_cleanup: true 65 | on: 66 | branch: master 67 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 68 | 69 | notifications: 70 | email: 71 | recipients: 72 | - build@tarantool.org 73 | on_success: change 74 | on_failure: always -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Denis Linnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tarantool-authman 2 | 3 | [![Build Status](https://travis-ci.org/mailru/tarantool-authman.png?branch=master)](https://travis-ci.org/mailru/tarantool-authman) 4 | 5 | This module provides api for registration and login users in your site (or any other system). 6 | Supports login with email/password and socials (facebook.com, vk.com, google.com). 7 | It also provides api that helps to build OAuth2 authentication service, 8 | i.e. methods to manage OAuth entities such as OAuth client, authorization code and access token. 9 | 10 | Requires tarantool >= 1.7.4.168 11 | 12 | ## Installation 13 | 14 | Using tarantoolctl: 15 | ``` 16 | $ tarantoolctl rocks install authman 17 | ``` 18 | 19 | Also available `.rpm` and `.deb` packages: 20 | ``` 21 | # CentOS 22 | $ yum install tarantool-authman 23 | 24 | # Ubuntu 25 | $ apt-get install tarantool-authman 26 | ``` 27 | 28 | ## Quickstart 29 | Create tarantool instance file /etc/tarantool/instances.available/tarantool-authman.lua: 30 | ``` 31 | box.cfg { 32 | listen = 3302, 33 | } 34 | 35 | 36 | local config = { 37 | -- configuration table, details below 38 | } 39 | auth = require('authman').api(config) 40 | 41 | ``` 42 | Create symlink in /etc/tarantool/instances.enabled: 43 | ``` 44 | $ ln -s /etc/tarantool/instances.available/tarantool-authman.lua . 45 | ``` 46 | 47 | Run tarantool and connect to it: 48 | ``` 49 | $ tarantoolctl start tarantool-authman 50 | $ tarantoolctl enter tarantool-authman 51 | 52 | ``` 53 | 54 | Use auth api: 55 | ``` 56 | tarantool> ok, user = auth.registration('example@mail.ru') 57 | tarantool> ok, user = auth.complete_registration('example@mail.ru', user.code, 'Pa$$wOrD') 58 | tarantool> ok, user = auth.set_profile(user['id'], {first_name='name', laste_name='surname'}) 59 | tarantool> ok, user = auth.auth('example@mail.ru', 'Pa$$wOrD') 60 | tarantool> session = user['session'] 61 | tarantool> ok, user = auth.check_auth(session) -- user can get new session 62 | ``` 63 | 64 | ## Configuration 65 | Example of my_config.lua module, fill empty strings with your values: 66 | ``` 67 | return { 68 | activation_secret = '', 69 | session_secret = '', 70 | restore_secret = '', 71 | session_lifetime = 7 * 24 * 60 * 60, 72 | session_update_timedelta = 2 * 24 * 60* 60, 73 | social_check_time = 60 * 60* 24, 74 | 75 | -- password_strength can be: 76 | -- none, whocares, easy, common, moderate, violence, nightmare, 77 | password_strength = 'common', -- default value 78 | 79 | facebook = { 80 | client_id = '', 81 | client_secret = '', 82 | redirect_uri='', 83 | }, 84 | google = { 85 | client_id = '', 86 | client_secret = '', 87 | redirect_uri='' 88 | }, 89 | vk = { 90 | client_id = '', 91 | client_secret = '', 92 | redirect_uri='', 93 | }, 94 | } 95 | ``` 96 | ## Api methods 97 | 98 | #### auth.registration(email) 99 | ``` 100 | tarantool> ok, user = auth.registration('aaa@mail.ru') 101 | tarantool> user.code 102 | - 022c1ff1f0b171e51cb6c6e32aefd6ab 103 | ``` 104 | Creates user, return user with code for email confirmation. 105 | 106 | #### auth.complete_registration(email, code, password) 107 | ``` 108 | tarantool> ok, user = auth.complete_registration('aaa@mail.ru', code, '123') 109 | tarantool> user 110 | - is_active: true 111 | email: aaa@mail.ru 112 | id: bcb6e00a-1148-4b7d-9ab1-9a9a3b21ce2a 113 | ``` 114 | Set user is_active=true and password, return user table (without session) 115 | 116 | #### auth.set_profile(user_id, profile_table) 117 | ``` 118 | tarantool> ok, user = auth.set_profile(id, {first_name='name', last_name='surname'}) 119 | tarantool> user 120 | - is_active: true 121 | email: aaa@mail.ru 122 | profile: {'first_name': 'name', last_name='surname'} 123 | id: bcb6e00a-1148-4b7d-9ab1-9a9a3b21ce2a 124 | 125 | ``` 126 | Set user profile first_name and last_name 127 | 128 | #### auth.get_profile(user_id) 129 | ``` 130 | tarantool> ok, user = auth.get_profile(id) 131 | tarantool> user 132 | --- 133 | - is_active: true 134 | email: aaa@mail.ru 135 | profile: {'first_name': 'name', last_name='surname'} 136 | id: bcb6e00a-1148-4b7d-9ab1-9a9a3b21ce2a 137 | ... 138 | 139 | ``` 140 | Get user profile details by id 141 | 142 | #### auth.delete_user(user_id) 143 | ``` 144 | tarantool> auth.delete_user(id) 145 | --- 146 | - is_active: true 147 | email: aaa@mail.ru 148 | profile: {'first_name': 'name', last_name='surname'} 149 | id: bcb6e00a-1148-4b7d-9ab1-9a9a3b21ce2a 150 | ... 151 | 152 | ``` 153 | Delete user from space 154 | 155 | #### auth.auth(email, password) 156 | ``` 157 | tarantool> ok, user = auth.auth('aaa@mail.ru', '123') 158 | tarantool> user 159 | - is_active: true 160 | email: aaa@mail.ru 161 | session:'eyJ1c2VyX2lkIjoiYjhjOWVlOWQtYWUxN....' 162 | id: b8c9ee9d-ae15-469d-a16f-415594121ece 163 | 164 | ``` 165 | Sign in user, return user table (with session) 166 | 167 | #### auth.check_auth(session) 168 | ``` 169 | tarantool> ok, user = auth.check_auth(session) 170 | tarantool> user 171 | - is_active: true 172 | email: aaa@mail.ru 173 | session: 'eyJ1c2VyX2lkIjoiYjhjOWVlOWQtYWUxNS00Nj.....' 174 | id: b8c9ee9d-ae15-469d-a16f-415594121ece 175 | ``` 176 | Check user is signed in, return user table (with new session) 177 | 178 | Session can be renewed, so set it again after each call oh this method 179 | 180 | User will contain social info, if passed session generated by using auth.social_auth() method: 181 | 182 | ``` 183 | tarantool> user 184 | - is_active: true 185 | social: 186 | provider: facebook 187 | social_id: '123123....' 188 | email: aaa@mail.ru 189 | session: 'eyJ1c2VyX2lkIjoiYjhjOWVlOWQtYWUxNS00Nj.....' 190 | id: b8c9ee9d-ae15-469d-a16f-415594121ece 191 | ``` 192 | 193 | #### auth.drop_session(session) 194 | ``` 195 | tarantool> ok, deleted = auth.drop_session(session) 196 | tarantool> deleted 197 | - true 198 | ``` 199 | Drop user session 200 | 201 | #### auth.restore_password(email) 202 | ``` 203 | tarantool> ok, token = auth.restore_password('aaa@mail.ru') 204 | tarantool> token 205 | - 8b9c03b7786a465e2175bb1a8bd8a59f 206 | ``` 207 | Get restore password token 208 | 209 | #### auth.complete_restore_password(email, token, password) 210 | 211 | ``` 212 | tarantool> ok, user = auth.complete_restore_password('aaa@mail.ru', code, '234') 213 | tarantool> user 214 | - is_active: true 215 | email: aaa@mail.ru 216 | id: bcb6e00a-1148-4b7d-9ab1-9a9a3b21ce2a 217 | ``` 218 | Set new password, return user table (without session) 219 | 220 | #### auth.social_auth_url(provider, state) 221 | 222 | ``` 223 | tarantool> ok, url = auth.social_auth_url('facebook', 'some-state-string') 224 | tarantool> url 225 | - https://www.facebook.com/v2.8/dialog/oauth?client_id=1813230128941062&redirect_uri={redirect}&scope=email&state=some-state-string 226 | ``` 227 | Return url for social auth. State is optional but recommended for csrf protection. 228 | 229 | #### auth.social_auth(provider, code) 230 | 231 | ``` 232 | tarantool> ok, user = auth.social_auth('facebook', code) 233 | tarantool> user 234 | - is_active: true 235 | social: 236 | provider: facebook 237 | social_id: '123123....' 238 | profile: {'first_name': 'a', 'last_name': 'aa'} 239 | id: bcb6e00a-1148-4b7d-9ab1-9a9a3b21ce2a 240 | email: aaa@mail.ru 241 | session: 'eyJ1c2VyX2lkIj.....' 242 | ``` 243 | Sign in user, return user table (with session) 244 | 245 | ## Documentation for OAuth2 related API 246 | 247 | [OAuth2](doc/oauth2.md) 248 | 249 | 250 | ## Handling errors: 251 | 252 | If first parametr (ok - bool) is false then error is occured. Error description will be stored in second param like: 253 | ``` 254 | error = {code = description} 255 | ``` 256 | Example: 257 | ``` 258 | tarantool> ok, user = auth.registration('aaa@mail.ru') 259 | tarantool> ok 260 | - false 261 | tarantool> user 262 | - '2': User already exists 263 | ``` 264 | Complete list of error codes: 265 | ``` 266 | error.USER_NOT_FOUND = '1' 267 | error.USER_ALREADY_EXISTS = '2' 268 | error.INVALID_PARAMS = '3' 269 | error.USER_NOT_ACTIVE = '4' 270 | error.WRONG_PASSWORD = '5' 271 | error.WRONG_ACTIVATION_CODE = '6' 272 | error.WRONG_SESSION_SIGN = '7' 273 | error.NOT_AUTHENTICATED = '8' 274 | error.WRONG_RESTORE_TOKEN = '9' 275 | error.USER_ALREADY_ACTIVE = '10' 276 | error.WRONG_AUTH_CODE = '11' 277 | error.IMPROPERLY_CONFIGURED = '12' 278 | error.WRONG_PROVIDER = '13' 279 | error.WEAK_PASSWORD = '14' 280 | error.SOCIAL_AUTH_ERROR = '15' 281 | error.OAUTH_APP_ALREADY_EXISTS = '16' 282 | error.OAUTH_APP_NOT_FOUND = '17' 283 | error.OAUTH_CONSUMER_NOT_FOUND = '18' 284 | error.OAUTH_MAX_APPS_REACHED = '19' 285 | error.OAUTH_CODE_NOT_FOUND = '20' 286 | error.OAUTH_ACCESS_TOKEN_NOT_FOUND = '21' 287 | ``` 288 | 289 | ## Run tests: 290 | To perform tests run this in directory with tarantool-authman module: 291 | ``` 292 | $ tarantool test/authman.test.lua 293 | ``` 294 | -------------------------------------------------------------------------------- /authman-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'authman' 2 | version = 'scm-1' 3 | source = { 4 | url = 'git://github.com/mailru/tarantool-authman.git', 5 | branch = 'master', 6 | } 7 | description = { 8 | summary = 'Auth module for Tarantool', 9 | homepage = 'https://github.com/mailru/tarantool-authman.git', 10 | license = 'MIT', 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | } 15 | build = { 16 | type = 'builtin', 17 | 18 | modules = { 19 | ['authman.migrations.migrations'] = 'authman/migrations/migrations.lua', 20 | ['authman.model.password'] = 'authman/model/password.lua', 21 | ['authman.model.password_token'] = 'authman/model/password_token.lua', 22 | ['authman.model.session'] = 'authman/model/session.lua', 23 | ['authman.model.social'] = 'authman/model/social.lua', 24 | ['authman.model.user'] = 'authman/model/user.lua', 25 | ['authman.model.oauth.app'] = 'authman/model/oauth/app.lua', 26 | ['authman.model.oauth.code'] = 'authman/model/oauth/code.lua', 27 | ['authman.model.oauth.token'] = 'authman/model/oauth/token.lua', 28 | ['authman.model.oauth.consumer'] = 'authman/model/oauth/consumer.lua', 29 | ['authman.model.oauth.consumer.redirect'] = 'authman/model/oauth/consumer/redirect.lua', 30 | ['authman.model.oauth.consumer.scope'] = 'authman/model/oauth/consumer/scope.lua', 31 | ['authman.utils.http'] = 'authman/utils/http.lua', 32 | ['authman.utils.utils'] = 'authman/utils/utils.lua', 33 | ['authman.validator'] = 'authman/validator.lua', 34 | ['authman.response'] = 'authman/response.lua', 35 | ['authman.error'] = 'authman/error.lua', 36 | ['authman.db'] = 'authman/db.lua', 37 | ['authman.oauth.oauth'] = 'authman/oauth/oauth.lua', 38 | ['authman'] = 'authman/init.lua' 39 | } 40 | } 41 | 42 | -- vim: syntax=lua 43 | -------------------------------------------------------------------------------- /authman/db.lua: -------------------------------------------------------------------------------- 1 | local db = {} 2 | 3 | 4 | function db.configurate(config) 5 | local api = {} 6 | 7 | local user = require('authman.model.user').model(config) 8 | local password = require('authman.model.password').model(config) 9 | local password_token = require('authman.model.password_token').model(config) 10 | local social = require('authman.model.social').model(config) 11 | local session = require('authman.model.session').model(config) 12 | local oauth_app = require('authman.model.oauth.app').model(config) 13 | local oauth_consumer = require('authman.model.oauth.consumer').model(config) 14 | local oauth_code = require('authman.model.oauth.code').model(config) 15 | local oauth_token = require('authman.model.oauth.token').model(config) 16 | local oauth_scope = require('authman.model.oauth.consumer.scope').model(config) 17 | local oauth_redirect = require('authman.model.oauth.consumer.redirect').model(config) 18 | 19 | function api.create_database() 20 | local user_space = box.schema.space.create(user.SPACE_NAME, { 21 | if_not_exists = true 22 | }) 23 | user_space:create_index(user.PRIMARY_INDEX, { 24 | type = 'hash', 25 | parts = {user.ID, 'string'}, 26 | if_not_exists = true 27 | }) 28 | user_space:create_index(user.EMAIL_INDEX, { 29 | type = 'tree', 30 | unique = false, 31 | parts = {user.EMAIL, 'string', user.TYPE, 'unsigned'}, 32 | if_not_exists = true 33 | }) 34 | 35 | local password_space = box.schema.space.create(password.SPACE_NAME, { 36 | if_not_exists = true 37 | }) 38 | password_space:create_index(password.PRIMARY_INDEX, { 39 | type = 'hash', 40 | parts = {password.ID, 'string'}, 41 | if_not_exists = true 42 | }) 43 | password_space:create_index(password.USER_ID_INDEX, { 44 | type = 'tree', 45 | unique = true, 46 | parts = {password.USER_ID, 'string'}, 47 | if_not_exists = true 48 | }) 49 | 50 | local reset_pwd_token_space = box.schema.space.create(password_token.SPACE_NAME, { 51 | if_not_exists = true 52 | }) 53 | reset_pwd_token_space:create_index(password_token.PRIMARY_INDEX, { 54 | type = 'hash', 55 | parts = {password_token.USER_ID, 'string'}, 56 | if_not_exists = true 57 | }) 58 | 59 | local social_space = box.schema.space.create(social.SPACE_NAME, { 60 | if_not_exists = true 61 | }) 62 | social_space:create_index(social.PRIMARY_INDEX, { 63 | type = 'hash', 64 | parts = {social.ID, 'string'}, 65 | if_not_exists = true 66 | }) 67 | social_space:create_index(social.USER_ID_INDEX, { 68 | type = 'tree', 69 | unique = true, 70 | parts = {social.USER_ID, 'string', social.PROVIDER, 'string'}, 71 | if_not_exists = true 72 | }) 73 | social_space:create_index(social.SOCIAL_INDEX, { 74 | type = 'hash', 75 | unique = true, 76 | parts = {social.SOCIAL_ID, 'string', social.PROVIDER, 'string'}, 77 | if_not_exists = true 78 | }) 79 | 80 | local session_space = box.schema.space.create(session.SPACE_NAME, { 81 | if_not_exists = true 82 | }) 83 | session_space:create_index(session.PRIMARY_INDEX, { 84 | type = 'hash', 85 | parts = {session.ID, 'string'}, 86 | if_not_exists = true 87 | }) 88 | session_space:create_index(session.USER_ID_INDEX, { 89 | type = 'tree', 90 | parts = {session.USER_ID, 'string'}, 91 | unique = false, 92 | if_not_exists = true 93 | }) 94 | 95 | local app_space = box.schema.space.create(oauth_app.SPACE_NAME, { 96 | if_not_exists = true 97 | }) 98 | app_space:create_index(oauth_app.PRIMARY_INDEX, { 99 | type = 'hash', 100 | parts = {oauth_app.ID, 'string'}, 101 | if_not_exists = true 102 | }) 103 | app_space:create_index(oauth_app.USER_ID_INDEX, { 104 | type = 'tree', 105 | unique = true, 106 | parts = {oauth_app.USER_ID, 'string', oauth_app.NAME, 'string'}, 107 | if_not_exists = true 108 | }) 109 | 110 | local oauth_consumer_space = box.schema.space.create(oauth_consumer.SPACE_NAME, { 111 | if_not_exists = true 112 | }) 113 | 114 | oauth_consumer_space:create_index(oauth_consumer.PRIMARY_INDEX, { 115 | type = 'hash', 116 | parts = {oauth_consumer.ID, 'string'}, 117 | if_not_exists = true 118 | }) 119 | oauth_consumer_space:create_index(oauth_consumer.APP_ID_INDEX, { 120 | type = 'tree', 121 | unique = true, 122 | parts = {oauth_consumer.APP_ID, 'string'}, 123 | if_not_exists = true 124 | }) 125 | 126 | local oauth_code_space = box.schema.space.create(oauth_code.SPACE_NAME, { 127 | if_not_exists = true 128 | }) 129 | 130 | oauth_code_space:create_index(oauth_code.PRIMARY_INDEX, { 131 | type = 'hash', 132 | parts = {oauth_code.CODE, 'string'}, 133 | if_not_exists = true 134 | }) 135 | oauth_code_space:create_index(oauth_code.CONSUMER_INDEX, { 136 | type = 'tree', 137 | unique = false, 138 | parts = {oauth_code.CONSUMER_KEY, 'string', oauth_code.RESOURCE_OWNER, 'string'}, 139 | if_not_exists = true 140 | }) 141 | 142 | local oauth_token_space = box.schema.space.create(oauth_token.SPACE_NAME, { 143 | if_not_exists = true 144 | }) 145 | 146 | oauth_token_space:create_index(oauth_token.PRIMARY_INDEX, { 147 | type = 'hash', 148 | parts = {oauth_token.ACCESS_TOKEN, 'string'}, 149 | if_not_exists = true 150 | }) 151 | oauth_token_space:create_index(oauth_token.CONSUMER_INDEX, { 152 | type = 'tree', 153 | unique = false, 154 | parts = {oauth_token.CONSUMER_KEY, 'string', oauth_token.RESOURCE_OWNER, 'string'}, 155 | if_not_exists = true 156 | }) 157 | oauth_token_space:create_index(oauth_token.REFRESH_INDEX, { 158 | type = 'tree', 159 | unique = true, 160 | parts = {oauth_token.REFRESH_TOKEN, 'string'}, 161 | if_not_exists = true 162 | }) 163 | 164 | local oauth_scope_space = box.schema.space.create(oauth_scope.SPACE_NAME, { 165 | if_not_exists = true 166 | }) 167 | 168 | oauth_scope_space:create_index(oauth_scope.PRIMARY_INDEX, { 169 | type = 'tree', 170 | unique = true, 171 | parts = {oauth_scope.CONSUMER_KEY, 'string', oauth_scope.USER_ID, 'string', oauth_scope.NAME, 'string'}, 172 | if_not_exists = true 173 | }) 174 | oauth_scope_space:create_index(oauth_scope.USER_ID_INDEX, { 175 | type = 'tree', 176 | unique = false, 177 | parts = {oauth_scope.USER_ID, 'string'}, 178 | if_not_exists = true 179 | }) 180 | 181 | local oauth_redirect_space = box.schema.space.create(oauth_redirect.SPACE_NAME, { 182 | if_not_exists = true 183 | }) 184 | 185 | oauth_redirect_space:create_index(oauth_redirect.PRIMARY_INDEX, { 186 | type = 'tree', 187 | unique = true, 188 | parts = {oauth_redirect.CONSUMER_KEY, 'string', oauth_redirect.USER_ID, 'string'}, 189 | if_not_exists = true 190 | }) 191 | oauth_redirect_space:create_index(oauth_redirect.USER_ID_INDEX, { 192 | type = 'tree', 193 | unique = false, 194 | parts = {oauth_redirect.USER_ID, 'string'}, 195 | if_not_exists = true 196 | }) 197 | end 198 | 199 | function api.truncate_spaces() 200 | user.get_space():truncate() 201 | password_token.get_space():truncate() 202 | password.get_space():truncate() 203 | social.get_space():truncate() 204 | session.get_space():truncate() 205 | oauth_app.get_space():truncate() 206 | oauth_consumer.get_space():truncate() 207 | oauth_code.get_space():truncate() 208 | oauth_token.get_space():truncate() 209 | oauth_scope.get_space():truncate() 210 | oauth_redirect.get_space():truncate() 211 | end 212 | 213 | return api 214 | end 215 | 216 | return db 217 | -------------------------------------------------------------------------------- /authman/error.lua: -------------------------------------------------------------------------------- 1 | local error = {} 2 | 3 | error.USER_NOT_FOUND = '1' 4 | error.USER_ALREADY_EXISTS = '2' 5 | error.INVALID_PARAMS = '3' 6 | error.USER_NOT_ACTIVE = '4' 7 | error.WRONG_PASSWORD = '5' 8 | error.WRONG_ACTIVATION_CODE = '6' 9 | error.WRONG_SESSION_SIGN = '7' 10 | error.NOT_AUTHENTICATED = '8' 11 | error.WRONG_RESTORE_TOKEN = '9' 12 | error.USER_ALREADY_ACTIVE = '10' 13 | error.WRONG_AUTH_CODE = '11' 14 | error.IMPROPERLY_CONFIGURED = '12' 15 | error.WRONG_PROVIDER = '13' 16 | error.WEAK_PASSWORD = '14' 17 | error.SOCIAL_AUTH_ERROR = '15' 18 | error.OAUTH_APP_ALREADY_EXISTS = '16' 19 | error.OAUTH_APP_NOT_FOUND = '17' 20 | error.OAUTH_CONSUMER_NOT_FOUND = '18' 21 | error.OAUTH_MAX_APPS_REACHED = '19' 22 | error.OAUTH_CODE_NOT_FOUND = '20' 23 | error.OAUTH_ACCESS_TOKEN_NOT_FOUND = '21' 24 | 25 | error.CODES = { 26 | [error.USER_NOT_FOUND] = 'User not found', 27 | [error.USER_ALREADY_EXISTS] = 'User already exists', 28 | [error.INVALID_PARAMS] = 'Invalid params', 29 | [error.USER_NOT_ACTIVE] = 'User not activated', 30 | [error.WRONG_PASSWORD] = 'Wrong password', 31 | [error.WRONG_ACTIVATION_CODE] = 'Wrong activation code', 32 | [error.WRONG_SESSION_SIGN] = 'Wrong session sign', 33 | [error.NOT_AUTHENTICATED] = 'User is not authenticated', 34 | [error.WRONG_RESTORE_TOKEN] = 'Wrong restore token', 35 | [error.USER_ALREADY_ACTIVE] = 'User already active', 36 | [error.WRONG_AUTH_CODE] = 'Wrong auth code', 37 | [error.IMPROPERLY_CONFIGURED] = 'Wrong config passed', 38 | [error.WRONG_PROVIDER] = 'Wrong social provider', 39 | [error.WEAK_PASSWORD] = 'Password to weak', 40 | [error.SOCIAL_AUTH_ERROR] = 'Error during getting user info', 41 | [error.OAUTH_APP_ALREADY_EXISTS] = 'oauth app already exists', 42 | [error.OAUTH_APP_NOT_FOUND] = 'oauth app not found', 43 | [error.OAUTH_CONSUMER_NOT_FOUND] = 'OAUTH consumer not found', 44 | [error.OAUTH_MAX_APPS_REACHED] = 'Max oauth apps limit reached', 45 | [error.OAUTH_CODE_NOT_FOUND] = 'OAUTH authorization code not found', 46 | [error.OAUTH_ACCESS_TOKEN_NOT_FOUND] = 'OAUTH access token not found', 47 | } 48 | 49 | return error 50 | -------------------------------------------------------------------------------- /authman/init.lua: -------------------------------------------------------------------------------- 1 | local auth = {} 2 | local response = require('authman.response') 3 | local error = require('authman.error') 4 | local validator = require('authman.validator') 5 | local db = require('authman.db') 6 | local utils = require('authman.utils.utils') 7 | 8 | 9 | function auth.api(config) 10 | local api = {} 11 | 12 | config = validator.config(config) 13 | local user = require('authman.model.user').model(config) 14 | local password = require('authman.model.password').model(config) 15 | local password_token = require('authman.model.password_token').model(config) 16 | local social = require('authman.model.social').model(config) 17 | local session = require('authman.model.session').model(config) 18 | local oauth_app = require('authman.model.oauth.app').model(config) 19 | local oauth = require('authman.oauth.oauth')(config) 20 | 21 | api.oauth = oauth 22 | 23 | db.configurate(config).create_database() 24 | require('authman.migrations.migrations')(config) 25 | 26 | ----------------- 27 | -- API methods -- 28 | ----------------- 29 | function api.registration(email, user_id) 30 | email = utils.lower(email) 31 | 32 | if not validator.email(email) then 33 | return response.error(error.INVALID_PARAMS) 34 | end 35 | 36 | local user_tuple = user.get_by_email(email, user.COMMON_TYPE) 37 | if user_tuple ~= nil then 38 | if user_tuple[user.IS_ACTIVE] and user_tuple[user.TYPE] == user.COMMON_TYPE then 39 | return response.error(error.USER_ALREADY_EXISTS) 40 | else 41 | local code = user.generate_activation_code(user_tuple[user.ID]) 42 | return response.ok(user.serialize(user_tuple, {code=code})) 43 | end 44 | end 45 | 46 | user_tuple = { 47 | [user.EMAIL] = email, 48 | [user.TYPE] = user.COMMON_TYPE, 49 | [user.IS_ACTIVE] = false, 50 | } 51 | if validator.not_empty_string(user_id) then 52 | user_tuple[user.ID] = user_id 53 | end 54 | 55 | user_tuple = user.create(user_tuple) 56 | 57 | local code = user.generate_activation_code(user_tuple[user.ID]) 58 | return response.ok(user.serialize(user_tuple, {code=code})) 59 | end 60 | 61 | function api.complete_registration(email, code, raw_password) 62 | email = utils.lower(email) 63 | 64 | if not (validator.email(email) and validator.not_empty_string(code)) then 65 | return response.error(error.INVALID_PARAMS) 66 | end 67 | 68 | if not password.strong_enough(raw_password) then 69 | return response.error(error.WEAK_PASSWORD) 70 | end 71 | 72 | local user_tuple = user.get_by_email(email, user.COMMON_TYPE) 73 | if user_tuple == nil then 74 | return response.error(error.USER_NOT_FOUND) 75 | end 76 | 77 | if user_tuple[user.IS_ACTIVE] then 78 | return response.error(error.USER_ALREADY_ACTIVE) 79 | end 80 | 81 | local user_id = user_tuple[user.ID] 82 | local correct_code = user.generate_activation_code(user_id) 83 | if code ~= correct_code then 84 | return response.error(error.WRONG_ACTIVATION_CODE) 85 | end 86 | 87 | password.create_or_update({ 88 | [password.USER_ID] = user_id, 89 | [password.HASH] = password.hash(raw_password, user_id) 90 | }) 91 | 92 | user_tuple = user.update({ 93 | [user.ID] = user_id, 94 | [user.IS_ACTIVE] = true, 95 | [user.REGISTRATION_TS] = utils.now(), 96 | }) 97 | 98 | return response.ok(user.serialize(user_tuple)) 99 | end 100 | 101 | function api.set_profile(user_id, user_profile) 102 | if not validator.not_empty_string(user_id) then 103 | return response.error(error.INVALID_PARAMS) 104 | end 105 | 106 | local user_tuple = user.get_by_id(user_id) 107 | if user_tuple == nil then 108 | return response.error(error.USER_NOT_FOUND) 109 | end 110 | 111 | if not user_tuple[user.IS_ACTIVE] then 112 | return response.error(error.USER_NOT_ACTIVE) 113 | end 114 | 115 | user_tuple = user.update({ 116 | [user.ID] = user_id, 117 | [user.PROFILE] = { 118 | [user.PROFILE_FIRST_NAME] = user_profile['first_name'], 119 | [user.PROFILE_LAST_NAME] = user_profile['last_name'], 120 | }, 121 | }) 122 | 123 | return response.ok(user.serialize(user_tuple)) 124 | end 125 | 126 | function api.get_profile(user_id) 127 | if not validator.not_empty_string(user_id) then 128 | return response.error(error.INVALID_PARAMS) 129 | end 130 | 131 | local user_tuple = user.get_by_id(user_id) 132 | if user_tuple == nil then 133 | return response.error(error.USER_NOT_FOUND) 134 | end 135 | 136 | return response.ok(user.serialize(user_tuple)) 137 | end 138 | 139 | function api.delete_user(user_id) 140 | if not validator.not_empty_string(user_id) then 141 | return response.error(error.INVALID_PARAMS) 142 | end 143 | 144 | local user_tuple = user.get_by_id(user_id) 145 | if user_tuple == nil then 146 | return response.error(error.USER_NOT_FOUND) 147 | end 148 | 149 | user.delete(user_id) 150 | password.delete_by_user_id(user_id) 151 | social.delete_by_user_id(user_id) 152 | password_token.delete(user_id) 153 | oauth_app.delete_by_user_id(user_id) 154 | 155 | return response.ok(user.serialize(user_tuple)) 156 | end 157 | 158 | function api.auth(email, raw_password) 159 | email = utils.lower(email) 160 | 161 | local user_tuple = user.get_by_email(email, user.COMMON_TYPE) 162 | if user_tuple == nil then 163 | return response.error(error.USER_NOT_FOUND) 164 | end 165 | 166 | if not user_tuple[user.IS_ACTIVE] then 167 | return response.error(error.USER_NOT_ACTIVE) 168 | end 169 | 170 | if not password.is_valid(raw_password, user_tuple[user.ID]) then 171 | return response.error(error.WRONG_PASSWORD) 172 | end 173 | 174 | local signed_session = session.create(user_tuple[user.ID], session.COMMON_SESSION_TYPE) 175 | user.update_session_ts(user_tuple) 176 | 177 | return response.ok(user.serialize(user_tuple, {session = signed_session})) 178 | end 179 | 180 | function api.check_auth(signed_session) 181 | if not validator.not_empty_string(signed_session) then 182 | return response.error(error.INVALID_PARAMS) 183 | end 184 | 185 | local encoded_session_data = session.validate_session(signed_session) 186 | 187 | if encoded_session_data == nil then 188 | return response.error(error.WRONG_SESSION_SIGN) 189 | end 190 | 191 | local session_tuple = session.get_by_session(encoded_session_data) 192 | if session_tuple == nil then 193 | return response.error(error.NOT_AUTHENTICATED) 194 | end 195 | 196 | local user_tuple = user.get_by_id(session_tuple[session.USER_ID]) 197 | if user_tuple == nil then 198 | return response.error(error.USER_NOT_FOUND) 199 | end 200 | 201 | if not user_tuple[user.IS_ACTIVE] then 202 | return response.error(error.USER_NOT_ACTIVE) 203 | end 204 | 205 | 206 | local session_data = session.decode(encoded_session_data) 207 | local new_session 208 | if session_data.type == session.SOCIAL_SESSION_TYPE then 209 | 210 | local social_tuple = social.get_by_id(session_tuple[session.CREDENTIAL_ID]) 211 | if social_tuple == nil then 212 | return response.error(error.USER_NOT_FOUND) 213 | end 214 | 215 | if session.is_expired(session_data) then 216 | return response.error(error.NOT_AUTHENTICATED) 217 | 218 | elseif session.need_social_update(session_data) then 219 | 220 | local updated_user_tuple = {user_tuple[user.ID]} 221 | local social_id = social.get_profile_info( 222 | social_tuple[social.PROVIDER], social_tuple[social.TOKEN], updated_user_tuple 223 | ) 224 | 225 | if social_id == nil then 226 | return response.error(error.NOT_AUTHENTICATED) 227 | end 228 | 229 | user_tuple = user.update(updated_user_tuple) 230 | new_session = session.create( 231 | user_tuple[user.ID], session.SOCIAL_SESSION_TYPE, social_tuple[social.ID] 232 | ) 233 | 234 | user.update_session_ts(user_tuple) 235 | 236 | else 237 | new_session = signed_session 238 | end 239 | 240 | social_tuple = social.get_by_user_id(user_tuple[user.ID]) 241 | 242 | return response.ok( 243 | user.serialize(user_tuple, { 244 | session = new_session, 245 | social = social.serialize(social_tuple), 246 | }) 247 | ) 248 | 249 | else 250 | 251 | if session.is_expired(session_data) then 252 | return response.error(error.NOT_AUTHENTICATED) 253 | 254 | elseif session.need_common_update(session_data) then 255 | new_session = session.create(user_tuple[user.ID], session.COMMON_SESSION_TYPE) 256 | user.update_session_ts(user_tuple) 257 | else 258 | new_session = signed_session 259 | end 260 | 261 | return response.ok(user.serialize(user_tuple, {session = new_session})) 262 | 263 | end 264 | end 265 | 266 | function api.drop_session(signed_session) 267 | if not validator.not_empty_string(signed_session) then 268 | return response.error(error.INVALID_PARAMS) 269 | end 270 | 271 | local encoded_session_data = session.validate_session(signed_session) 272 | 273 | if encoded_session_data == nil then 274 | return response.error(error.WRONG_SESSION_SIGN) 275 | end 276 | 277 | local deleted = session.delete(encoded_session_data) 278 | return response.ok(deleted) 279 | end 280 | 281 | function api.restore_password(email) 282 | email = utils.lower(email) 283 | 284 | local user_tuple = user.get_by_email(email, user.COMMON_TYPE) 285 | 286 | if user_tuple == nil then 287 | return response.error(error.USER_NOT_FOUND) 288 | end 289 | 290 | if not user_tuple[user.IS_ACTIVE] then 291 | return response.error(error.USER_NOT_ACTIVE) 292 | end 293 | return response.ok(password_token.generate(user_tuple[user.ID])) 294 | end 295 | 296 | function api.complete_restore_password(email, token, raw_password) 297 | email = utils.lower(email) 298 | 299 | if not validator.not_empty_string(token) then 300 | return response.error(error.INVALID_PARAMS) 301 | end 302 | 303 | local user_tuple = user.get_by_email(email, user.COMMON_TYPE) 304 | 305 | if user_tuple == nil then 306 | return response.error(error.USER_NOT_FOUND) 307 | end 308 | 309 | if not user_tuple[user.IS_ACTIVE] then 310 | return response.error(error.USER_NOT_ACTIVE) 311 | end 312 | 313 | if not password.strong_enough(raw_password) then 314 | return response.error(error.WEAK_PASSWORD) 315 | end 316 | 317 | local user_id = user_tuple[user.ID] 318 | if password_token.is_valid(token, user_id) then 319 | 320 | password.create_or_update({ 321 | [password.USER_ID] = user_id, 322 | [password.HASH] = password.hash(raw_password, user_id) 323 | }) 324 | 325 | session.drop_by_user(user_id) 326 | 327 | user_tuple = user.update({ 328 | [user.ID] = user_id, 329 | [user.TYPE] = user.COMMON_TYPE, 330 | }) 331 | 332 | password_token.delete(user_id) 333 | 334 | return response.ok(user.serialize(user_tuple)) 335 | else 336 | return response.error(error.WRONG_RESTORE_TOKEN) 337 | end 338 | end 339 | 340 | function api.social_auth_url(provider, state) 341 | if not validator.provider(provider) then 342 | return response.error(error.WRONG_PROVIDER) 343 | end 344 | 345 | return response.ok(social.get_social_auth_url(provider, state)) 346 | end 347 | 348 | function api.social_auth(provider, code) 349 | local token, social_id, social_tuple 350 | local user_tuple = {} 351 | 352 | if not (validator.provider(provider) and validator.not_empty_string(code)) then 353 | return response.error(error.WRONG_PROVIDER) 354 | end 355 | 356 | token = social.get_token(provider, code, user_tuple) 357 | if not validator.not_empty_string(token) then 358 | return response.error(error.WRONG_AUTH_CODE) 359 | end 360 | 361 | social_id = social.get_profile_info(provider, token, user_tuple) 362 | if not validator.not_empty_string(social_id) then 363 | return response.error(error.SOCIAL_AUTH_ERROR) 364 | end 365 | 366 | local now = utils.now() 367 | user_tuple[user.EMAIL] = utils.lower(user_tuple[user.EMAIL]) 368 | user_tuple[user.IS_ACTIVE] = true 369 | user_tuple[user.TYPE] = user.SOCIAL_TYPE 370 | user_tuple[user.SESSION_UPDATE_TS] = now 371 | 372 | social_tuple = social.get_by_social_id(social_id, provider) 373 | if social_tuple == nil then 374 | user_tuple = user.create(user_tuple) 375 | social_tuple = social.create({ 376 | [social.USER_ID] = user_tuple[user.ID], 377 | [social.PROVIDER] = provider, 378 | [social.SOCIAL_ID] = social_id, 379 | [social.TOKEN] = token, 380 | }) 381 | else 382 | user_tuple[user.ID] = social_tuple[social.USER_ID] 383 | user_tuple = user.create_or_update(user_tuple) 384 | social_tuple = social.update({ 385 | [social.ID] = social_tuple[social.ID], 386 | [social.USER_ID] = user_tuple[user.ID], 387 | [social.TOKEN] = token, 388 | }) 389 | end 390 | 391 | local new_session = session.create( 392 | user_tuple[user.ID], session.SOCIAL_SESSION_TYPE, social_tuple[social.ID] 393 | ) 394 | 395 | return response.ok(user.serialize(user_tuple, { 396 | session = new_session, 397 | social = social.serialize(social_tuple), 398 | })) 399 | end 400 | 401 | return api 402 | end 403 | 404 | return auth 405 | -------------------------------------------------------------------------------- /authman/migrations/migrations.lua: -------------------------------------------------------------------------------- 1 | local utils = require('authman.utils.utils') 2 | local fiber = require('fiber') 3 | 4 | return function(config) 5 | local user = require('authman.model.user').model(config) 6 | local oauth_app = require('authman.model.oauth.app').model(config) 7 | local oauth_consumer = require('authman.model.oauth.consumer').model(config) 8 | local oauth_code = require('authman.model.oauth.code').model(config) 9 | local oauth_token = require('authman.model.oauth.token').model(config) 10 | 11 | if box.cfg.read_only == false then 12 | 13 | box.once('20170726_authman_add_registration_and_session_ts', function () 14 | local counter = 0 15 | local now = utils.now() 16 | for _, tuple in user.get_space():pairs(nil, {iterator=box.index.ALL}) do 17 | local user_tuple = tuple:totable() 18 | user_tuple[user.REGISTRATION_TS] = now 19 | user_tuple[user.SESSION_UPDATE_TS] = now 20 | user.get_space():replace(user_tuple) 21 | 22 | counter = counter + 1 23 | if counter % 10000 == 0 then 24 | fiber.sleep(0) 25 | end 26 | end 27 | end) 28 | 29 | box.once('20180125_authman_oauth_add_app_is_trusted', function () 30 | local counter = 0 31 | for _, tuple in oauth_app.get_space():pairs(nil, {iterator=box.index.ALL}) do 32 | local app_tuple = tuple:totable() 33 | app_tuple[oauth_app.IS_TRUSTED] = false 34 | oauth_app.get_space():replace(app_tuple) 35 | 36 | counter = counter + 1 37 | if counter % 10000 == 0 then 38 | fiber.sleep(0) 39 | end 40 | end 41 | end) 42 | 43 | box.once('20180125_authman_oauth_add_code_and_token_resource_owner', function () 44 | local counter = 0 45 | for _, tuple in oauth_code.get_space():pairs(nil, {iterator=box.index.ALL}) do 46 | local code_tuple = tuple:totable() 47 | local consumer_tuple = oauth_consumer.get_by_id(code_tuple[oauth_code.CONSUMER_KEY]) 48 | if consumer_tuple ~= nil then 49 | local app_tuple = oauth_app.get_by_id(consumer_tuple[oauth_consumer.APP_ID]) 50 | if app_tuple ~= nil then 51 | code_tuple[oauth_code.RESOURCE_OWNER] = app_tuple[oauth_app.USER_ID] 52 | oauth_code.get_space():replace(code_tuple) 53 | end 54 | end 55 | 56 | counter = counter + 1 57 | if counter % 10000 == 0 then 58 | fiber.sleep(0) 59 | end 60 | end 61 | for _, tuple in oauth_token.get_space():pairs(nil, {iterator=box.index.ALL}) do 62 | local token_tuple = tuple:totable() 63 | local consumer_tuple = oauth_consumer.get_by_id(token_tuple[oauth_token.CONSUMER_KEY]) 64 | if consumer_tuple ~= nil then 65 | local app_tuple = oauth_app.get_by_id(consumer_tuple[oauth_consumer.APP_ID]) 66 | if app_tuple ~= nil then 67 | token_tuple[oauth_token.RESOURCE_OWNER] = app_tuple[oauth_app.USER_ID] 68 | oauth_token.get_space():replace(token_tuple) 69 | end 70 | end 71 | 72 | counter = counter + 1 73 | if counter % 10000 == 0 then 74 | fiber.sleep(0) 75 | end 76 | end 77 | end) 78 | 79 | -- put migrations with box.once here 80 | 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /authman/model/oauth/app.lua: -------------------------------------------------------------------------------- 1 | local app = {} 2 | 3 | local uuid = require('uuid') 4 | local validator = require('authman.validator') 5 | local utils = require('authman.utils.utils') 6 | 7 | 8 | ----- 9 | -- app (uuid, user_uuid, type, is_active, registration_ts, is_trusted) 10 | ----- 11 | function app.model(config) 12 | 13 | local user = require('authman.model.user').model(config) 14 | local oauth_consumer = require('authman.model.oauth.consumer').model(config) 15 | local oauth_code = require('authman.model.oauth.code').model(config) 16 | local oauth_token = require('authman.model.oauth.token').model(config) 17 | 18 | local model = {} 19 | model.SPACE_NAME = config.spaces.oauth_app.name 20 | 21 | model.PRIMARY_INDEX = 'primary' 22 | model.USER_ID_INDEX = 'user' 23 | 24 | model.ID = 1 25 | model.USER_ID = 2 26 | model.NAME = 3 27 | model.TYPE = 4 28 | model.IS_ACTIVE = 5 29 | model.REGISTRATION_TS = 6 30 | model.IS_TRUSTED = 7 31 | 32 | model.DEFAULT_LIST_LIMIT = 10 33 | 34 | function model.get_space() 35 | return box.space[model.SPACE_NAME] 36 | end 37 | 38 | function model.serialize(app_tuple, data) 39 | 40 | local app_data = { 41 | id = app_tuple[model.ID], 42 | user_id = app_tuple[model.USER_ID], 43 | name = app_tuple[model.NAME], 44 | type = app_tuple[model.TYPE], 45 | is_active = app_tuple[model.IS_ACTIVE], 46 | is_trusted = app_tuple[model.IS_TRUSTED], 47 | } 48 | if data ~= nil then 49 | for k,v in pairs(data) do 50 | app_data[k] = v 51 | end 52 | end 53 | 54 | return app_data 55 | end 56 | 57 | 58 | function model.create(app_tuple) 59 | app_tuple[model.REGISTRATION_TS] = utils.now() 60 | 61 | local app_id = uuid.str() 62 | local app_type = validator.oauth_app_type(app_tuple[model.TYPE]) and app_tuple[model.TYPE] or 'browser' 63 | return model.get_space():insert{ 64 | app_id, 65 | app_tuple[model.USER_ID], 66 | app_tuple[model.NAME], 67 | app_tuple[model.TYPE], 68 | app_tuple[model.IS_ACTIVE], 69 | app_tuple[model.REGISTRATION_TS], 70 | app_tuple[model.IS_TRUSTED], 71 | } 72 | 73 | end 74 | 75 | function model.list(offset, limit) 76 | local data = {} 77 | local apps = model.get_space().index[model.PRIMARY_INDEX]:select(nil, {offset = offset, limit = limit, iterator = box.index.ALL}) 78 | if apps ~= nil and #apps ~= 0 then 79 | local users = {} 80 | for i, app in pairs(apps) do 81 | 82 | local app_user = users[app[model.USER_ID]] 83 | if not app_user then 84 | app_user = user.serialize(user.get_by_id(app[model.USER_ID])) 85 | users[app[model.USER_ID]] = app_user 86 | end 87 | 88 | local consumer_tuple = oauth_consumer.get_by_app_id(app[model.ID]) 89 | local extra_data = oauth_consumer.serialize(consumer_tuple, {user = app_user}) 90 | data[i] = model.serialize(app, extra_data) 91 | end 92 | end 93 | 94 | return data 95 | end 96 | 97 | function model.count_total() 98 | return model.get_space():len() 99 | end 100 | 101 | function model.get_by_user_id(user_id) 102 | if validator.not_empty_string(user_id) then 103 | return model.get_space().index[model.USER_ID_INDEX]:select({user_id}) 104 | end 105 | end 106 | 107 | function model.delete_by_user_id(user_id) 108 | 109 | local app_list = model.get_by_user_id(user_id) 110 | if app_list ~= nil then 111 | for i, tuple in ipairs(app_list) do 112 | local consumer = oauth_consumer.delete_by_app_id(tuple[model.ID]) 113 | 114 | if consumer ~= nil then 115 | oauth_code.delete_by_consumer_key(consumer[oauth_consumer.ID]) 116 | oauth_token.delete_by_consumer_key(consumer[oauth_consumer.ID]) 117 | end 118 | model.get_space():delete({tuple[model.ID]}) 119 | end 120 | return app_list 121 | end 122 | end 123 | 124 | function model.get_by_id(id) 125 | if validator.not_empty_string(id) then 126 | return model.get_space():get(id) 127 | end 128 | end 129 | 130 | function model.delete(id) 131 | if validator.not_empty_string(id) then 132 | return model.get_space():delete({id}) 133 | end 134 | end 135 | 136 | function model.update(app_tuple) 137 | local id, fields 138 | id = app_tuple[model.ID] 139 | fields = utils.format_update(app_tuple) 140 | return model.get_space():update(id, fields) 141 | end 142 | 143 | function model.load_by_consumer_keys(args) 144 | 145 | local res = {} 146 | local users = {} 147 | for _, consumer_key in ipairs(args) do 148 | 149 | local consumer = oauth_consumer.get_by_id(consumer_key) 150 | if consumer ~= nil then 151 | 152 | local app = model.get_by_id(consumer[oauth_consumer.APP_ID]) 153 | if app ~= nil then 154 | 155 | local current_user = users[app[model.USER_ID]] 156 | if not current_user then 157 | current_user = user.serialize(user.get_by_id(app[model.USER_ID])) 158 | users[app[model.USER_ID]] = current_user 159 | end 160 | 161 | local extra_data = oauth_consumer.serialize(consumer, {user = current_user}) 162 | res[consumer_key] = model.serialize(app, extra_data) 163 | end 164 | end 165 | end 166 | return res 167 | end 168 | 169 | 170 | return model 171 | end 172 | 173 | return app 174 | -------------------------------------------------------------------------------- /authman/model/oauth/code.lua: -------------------------------------------------------------------------------- 1 | local code = {} 2 | 3 | local validator = require('authman.validator') 4 | 5 | 6 | ----- 7 | -- oauth authorization code 8 | -- oauth_code (code, consumer_key, redirect_url, scope, state, expires_in, created_at, code_challenge, code_challenge_method) 9 | ----- 10 | function code.model(config) 11 | 12 | local model = {} 13 | model.SPACE_NAME = config.spaces.oauth_code.name 14 | 15 | model.PRIMARY_INDEX = 'primary' 16 | model.CONSUMER_INDEX = 'consumer' 17 | model.EXPIRES_INDEX = 'expires' 18 | 19 | model.CODE = 1 20 | model.CONSUMER_KEY = 2 21 | model.REDIRECT_URL = 3 22 | model.SCOPE = 4 23 | model.STATE = 5 24 | model.EXPIRES_IN = 6 25 | model.CREATED_AT = 7 26 | model.CODE_CHALLENGE = 8 27 | model.CODE_CHALLENGE_METHOD = 9 28 | model.RESOURCE_OWNER = 10 29 | 30 | model.CONSUMER_INDEX_CONSUMER_KEY = 1 31 | model.CONSUMER_INDEX_RESOURCE_OWNER = 2 32 | 33 | function model.get_space() 34 | return box.space[model.SPACE_NAME] 35 | end 36 | 37 | function model.serialize(code_tuple, data) 38 | 39 | local result = { 40 | code = code_tuple[model.CODE], 41 | consumer_key = code_tuple[model.CONSUMER_KEY], 42 | redirect_url = code_tuple[model.REDIRECT_URL], 43 | scope = code_tuple[model.SCOPE], 44 | state = code_tuple[model.STATE], 45 | expires_in = code_tuple[model.EXPIRES_IN], 46 | created_at = code_tuple[model.CREATED_AT], 47 | code_challenge = code_tuple[model.CODE_CHALLENGE], 48 | code_challenge_method = code_tuple[model.CODE_CHALLENGE_METHOD], 49 | resource_owner = code_tuple[model.RESOURCE_OWNER], 50 | } 51 | if data ~= nil then 52 | for k,v in pairs(data) do 53 | result[k] = v 54 | end 55 | end 56 | 57 | return result 58 | end 59 | 60 | function model.create(code_tuple) 61 | return model.get_space():insert(code_tuple) 62 | end 63 | 64 | function model.get_by_code(code) 65 | if validator.not_empty_string(code) then 66 | return model.get_space():get(code) 67 | end 68 | end 69 | 70 | function model.delete(code) 71 | if validator.not_empty_string(code) then 72 | return model.get_space():delete({code}) 73 | end 74 | end 75 | 76 | function model.get_by_consumer_key(consumer_key, resource_owner) 77 | if validator.not_empty_string(consumer_key) then 78 | local query = {[model.CONSUMER_INDEX_CONSUMER_KEY] = consumer_key} 79 | if validator.not_empty_string(resource_owner) then 80 | query[model.CONSUMER_INDEX_RESOURCE_OWNER] = resource_owner 81 | end 82 | return model.get_space().index[model.CONSUMER_INDEX]:select(query) 83 | end 84 | end 85 | 86 | function model.delete_by_consumer_key(consumer_key, resource_owner) 87 | local code_list = model.get_by_consumer_key(consumer_key, resource_owner) 88 | if code_list ~= nil then 89 | for i, tuple in ipairs(code_list) do 90 | model.get_space():delete({tuple[model.CODE]}) 91 | end 92 | return code_list 93 | end 94 | end 95 | 96 | function model.delete_expired(expiration_ts) 97 | local total = 0 98 | local total_deleted = 0 99 | for _, tuple in model.get_space().index[model.PRIMARY_INDEX]:pairs(nil, {iterator = box.index.ALL}) do 100 | 101 | if tuple[model.CREATED_AT] + tuple[model.EXPIRES_IN] <= expiration_ts then 102 | model.get_space():delete({tuple[model.CODE]}) 103 | total_deleted = total_deleted + 1 104 | end 105 | total = total + 1 106 | if math.fmod(total, 500) == 0 then 107 | fiber.yield() 108 | end 109 | end 110 | 111 | return total_deleted 112 | end 113 | 114 | return model 115 | end 116 | 117 | return code 118 | -------------------------------------------------------------------------------- /authman/model/oauth/consumer.lua: -------------------------------------------------------------------------------- 1 | local consumer = {} 2 | 3 | local validator = require('authman.validator') 4 | local utils = require('authman.utils.utils') 5 | local uuid = require('uuid') 6 | 7 | 8 | ----- 9 | -- oauth_consumer (id, secret, app_id, redirect_urls) 10 | ----- 11 | function consumer.model(config) 12 | 13 | local model = {} 14 | model.SPACE_NAME = config.spaces.oauth_consumer.name 15 | 16 | model.PRIMARY_INDEX = 'primary' 17 | model.APP_ID_INDEX = 'app' 18 | 19 | model.ID = 1 20 | model.SECRET_HASH = 2 21 | model.APP_ID = 3 22 | model.REDIRECT_URLS = 4 -- blank space separated list 23 | 24 | model.CONSUMER_SECRET_LEN = 32 25 | 26 | function model.get_space() 27 | return box.space[model.SPACE_NAME] 28 | end 29 | 30 | function model.serialize(consumer_tuple, data) 31 | if consumer_tuple == nil then 32 | return {} 33 | end 34 | 35 | local result = { 36 | consumer_key = consumer_tuple[model.ID], 37 | consumer_secret_hash = consumer_tuple[model.SECRET_HASH], 38 | app_id = consumer_tuple[model.APP_ID], 39 | redirect_urls = consumer_tuple[model.REDIRECT_URLS], 40 | } 41 | if data ~= nil then 42 | for k,v in pairs(data) do 43 | result[k] = v 44 | end 45 | end 46 | 47 | return result 48 | end 49 | 50 | 51 | function model.create(id, secret, app_id, redirect_urls) 52 | 53 | return model.get_space():insert{ 54 | id, 55 | utils.salted_hash(secret, app_id), 56 | app_id, 57 | redirect_urls, 58 | } 59 | end 60 | 61 | function model.get_by_id(id) 62 | if validator.not_empty_string(id) then 63 | return model.get_space():get(id) 64 | end 65 | end 66 | 67 | function model.get_by_app_id(app_id) 68 | if validator.not_empty_string(app_id) then 69 | return model.get_space().index[model.APP_ID_INDEX]:get(app_id) 70 | end 71 | end 72 | 73 | function model.generate_consumer_key() 74 | return string.hex(uuid.bin()) 75 | end 76 | 77 | function model.generate_consumer_secret() 78 | return utils.gen_random_key(model.CONSUMER_SECRET_LEN) 79 | end 80 | 81 | function model.update_consumer_secret(consumer_key, consumer_secret, app_id) 82 | local consumer_secret_hash = utils.salted_hash(consumer_secret, app_id) 83 | return model.get_space():update(consumer_key, {{'=', model.SECRET_HASH, consumer_secret_hash}}) 84 | end 85 | 86 | function model.delete_by_app_id(app_id) 87 | if validator.not_empty_string(app_id) then 88 | return model.get_space().index[model.APP_ID_INDEX]:delete(app_id) 89 | end 90 | end 91 | 92 | return model 93 | end 94 | 95 | return consumer 96 | -------------------------------------------------------------------------------- /authman/model/oauth/consumer/redirect.lua: -------------------------------------------------------------------------------- 1 | local redirect = {} 2 | 3 | local validator = require('authman.validator') 4 | 5 | 6 | ----- 7 | -- oauth consumer redirect url 8 | -- oauth_redirect (consumer_key, user_id, url) 9 | ----- 10 | function redirect.model(config) 11 | local model = {} 12 | model.SPACE_NAME = config.spaces.oauth_redirect.name 13 | 14 | model.PRIMARY_INDEX = 'primary' 15 | model.USER_ID_INDEX = 'user_id' 16 | 17 | model.CONSUMER_KEY = 1 18 | model.USER_ID = 2 19 | model.URL = 3 20 | 21 | function model.get_space() 22 | return box.space[model.SPACE_NAME] 23 | end 24 | 25 | function model.serialize(redirect_tuple, data) 26 | 27 | local redirect_data = { 28 | consumer_key = redirect_tuple[model.CONSUMER_KEY], 29 | user_id = redirect_tuple[model.USER_ID], 30 | url = redirect_tuple[model.URL], 31 | } 32 | 33 | if data ~= nil then 34 | for k,v in pairs(data) do 35 | redirect_data[k] = v 36 | end 37 | end 38 | 39 | return redirect_data 40 | end 41 | 42 | function model.upsert_redirect(redirect_tuple) 43 | model.get_space():upsert(redirect_tuple, {{'=', model.URL, redirect_tuple[model.URL]}}) 44 | return redirect_tuple 45 | end 46 | 47 | function model.get_by_user_id(user_id) 48 | return model.get_space().index[model.USER_ID_INDEX]:select({user_id}) 49 | end 50 | 51 | function model.delete_by_user_id(user_id) 52 | local redirect_list = {} 53 | local tuples = model.get_by_user_id(user_id) 54 | for i, tuple in ipairs(tuples) do 55 | redirect_list[i] = model.get_space():delete({[model.CONSUMER_KEY] = tuple[model.CONSUMER_KEY], [model.USER_ID] = tuple[model.USER_ID]}) 56 | end 57 | return redirect_list 58 | end 59 | 60 | function model.get_by_consumer_key(consumer_key, user_id) 61 | local query = {[model.CONSUMER_KEY] = consumer_key} 62 | if validator.not_empty_string(user_id) then 63 | query[model.USER_ID] = user_id 64 | end 65 | return model.get_space().index[model.PRIMARY_INDEX]:select(query) 66 | end 67 | 68 | function model.delete_by_consumer_key(consumer_key, user_id) 69 | local redirect_list = {} 70 | local tuples = model.get_by_consumer_key(consumer_key, user_id) 71 | for i, tuple in ipairs(tuples) do 72 | redirect_list[i] = model.get_space():delete({[model.CONSUMER_KEY] = tuple[model.CONSUMER_KEY], [model.USER_ID] = tuple[model.USER_ID]}) 73 | end 74 | return redirect_list 75 | end 76 | 77 | return model 78 | end 79 | 80 | return redirect 81 | -------------------------------------------------------------------------------- /authman/model/oauth/consumer/scope.lua: -------------------------------------------------------------------------------- 1 | local scope = {} 2 | 3 | local validator = require('authman.validator') 4 | 5 | 6 | ----- 7 | -- oauth consumer authorization scope 8 | -- oauth_scope (user_id, consumer_key, scope) 9 | ----- 10 | function scope.model(config) 11 | local model = {} 12 | model.SPACE_NAME = config.spaces.oauth_scope.name 13 | 14 | model.PRIMARY_INDEX = 'primary' 15 | model.USER_ID_INDEX = 'user_id' 16 | 17 | model.CONSUMER_KEY = 1 18 | model.USER_ID = 2 19 | model.NAME = 3 20 | 21 | function model.get_space() 22 | return box.space[model.SPACE_NAME] 23 | end 24 | 25 | function model.serialize(scope_tuples, data) 26 | 27 | local res = {} 28 | 29 | if scope_tuples ~= nil then 30 | for i, scope_tuple in ipairs(scope_tuples) do 31 | res[i] = { 32 | user_id = scope_tuple[model.USER_ID], 33 | consumer_key = scope_tuple[model.CONSUMER_KEY], 34 | name = scope_tuple[model.NAME], 35 | } 36 | 37 | if data and data[i] then 38 | for k,v in pairs(data[i]) do 39 | res[i][k] = v 40 | end 41 | end 42 | end 43 | end 44 | 45 | return res 46 | end 47 | 48 | function model.add_consumer_scopes(consumer_key, user_id, scopes) 49 | 50 | local cur_scopes = model.get_by_consumer_key(consumer_key, user_id) 51 | for _, scope_name in ipairs(scopes) do 52 | 53 | if validator.not_empty_string(scope_name) 54 | and not model.scope_exists(scope_name, cur_scopes) then 55 | 56 | local scope_tuple = { 57 | [model.CONSUMER_KEY] = consumer_key, 58 | [model.USER_ID] = user_id, 59 | [model.NAME] = scope_name, 60 | } 61 | 62 | table.insert(cur_scopes, model.get_space():replace(scope_tuple)) 63 | end 64 | end 65 | 66 | return cur_scopes 67 | end 68 | 69 | function model.scope_exists(scope_name, scopes) 70 | for _, scope in ipairs(scopes) do 71 | if scope[model.NAME] == scope_name then 72 | return true 73 | end 74 | end 75 | 76 | return false 77 | end 78 | 79 | function model.get_by_user_id(user_id) 80 | return model.get_space().index[model.USER_ID_INDEX]:select({user_id}) 81 | end 82 | 83 | function model.delete_by_user_id(user_id) 84 | local scope_list = {} 85 | local tuples = model.get_by_user_id(user_id) 86 | for i, tuple in ipairs(tuples) do 87 | scope_list[i] = model.get_space():delete({tuple[model.CONSUMER_KEY], tuple[model.USER_ID], tuple[model.NAME]}) 88 | end 89 | return scope_list 90 | end 91 | 92 | function model.get_by_consumer_key(consumer_key, user_id) 93 | local query = {[model.CONSUMER_KEY] = consumer_key} 94 | if validator.not_empty_string(user_id) then 95 | query[model.USER_ID] = user_id 96 | end 97 | return model.get_space().index[model.PRIMARY_INDEX]:select(query) 98 | end 99 | 100 | function model.delete_by_consumer_key(consumer_key, user_id) 101 | local scope_list = {} 102 | local tuples = model.get_by_consumer_key(consumer_key, user_id) 103 | for i, tuple in ipairs(tuples) do 104 | scope_list[i] = model.get_space():delete({tuple[model.CONSUMER_KEY], tuple[model.USER_ID], tuple[model.NAME]}) 105 | end 106 | return scope_list 107 | end 108 | 109 | return model 110 | end 111 | 112 | return scope 113 | -------------------------------------------------------------------------------- /authman/model/oauth/token.lua: -------------------------------------------------------------------------------- 1 | local token = {} 2 | 3 | local validator = require('authman.validator') 4 | 5 | 6 | ----- 7 | -- oauth access token 8 | -- oauth_token (access_token, consumer_key, refresh_token, redirect_url, scope, expires_in, created_at) 9 | ----- 10 | function token.model(config) 11 | 12 | local model = {} 13 | model.SPACE_NAME = config.spaces.oauth_token.name 14 | 15 | model.PRIMARY_INDEX = 'primary' 16 | model.REFRESH_INDEX = 'refresh' 17 | model.CONSUMER_INDEX = 'consumer' 18 | 19 | model.ACCESS_TOKEN = 1 20 | model.CONSUMER_KEY = 2 21 | model.REFRESH_TOKEN = 3 22 | model.REDIRECT_URL = 4 23 | model.SCOPE = 5 24 | model.EXPIRES_IN = 6 25 | model.CREATED_AT = 7 26 | model.RESOURCE_OWNER = 8 27 | 28 | model.CONSUMER_INDEX_CONSUMER_KEY = 1 29 | model.CONSUMER_INDEX_RESOURCE_OWNER = 2 30 | 31 | function model.get_space() 32 | return box.space[model.SPACE_NAME] 33 | end 34 | 35 | function model.serialize(token_tuple, data) 36 | 37 | local result = { 38 | access_token = token_tuple[model.ACCESS_TOKEN], 39 | consumer_key = token_tuple[model.CONSUMER_KEY], 40 | refresh_token = token_tuple[model.REFRESH_TOKEN], 41 | redirect_url = token_tuple[model.REDIRECT_URL], 42 | scope = token_tuple[model.SCOPE], 43 | expires_in = token_tuple[model.EXPIRES_IN], 44 | created_at = token_tuple[model.CREATED_AT], 45 | resource_owner = token_tuple[model.RESOURCE_OWNER], 46 | } 47 | if data ~= nil then 48 | for k,v in pairs(data) do 49 | result[k] = v 50 | end 51 | end 52 | 53 | return result 54 | end 55 | 56 | function model.create(token_tuple) 57 | return model.get_space():insert(token_tuple) 58 | end 59 | 60 | function model.get_by_access_token(access_token) 61 | if validator.not_empty_string(access_token) then 62 | return model.get_space():get(access_token) 63 | end 64 | end 65 | 66 | function model.get_by_refresh_token(refresh_token) 67 | if validator.not_empty_string(refresh_token) then 68 | return model.get_space().index[model.REFRESH_INDEX]:get(refresh_token) 69 | end 70 | end 71 | 72 | function model.delete(access_token) 73 | if validator.not_empty_string(access_token) then 74 | return model.get_space():delete(access_token) 75 | end 76 | end 77 | 78 | function model.delete_expired(expiration_ts) 79 | local total = 0 80 | local total_deleted = 0 81 | for _, tuple in model.get_space().index[model.PRIMARY_INDEX]:pairs(nil, {iterator = box.index.ALL}) do 82 | 83 | if tuple[model.CREATED_AT] + tuple[model.EXPIRES_IN] <= expiration_ts then 84 | model.get_space():delete({tuple[model.ACCESS_TOKEN]}) 85 | total_deleted = total_deleted + 1 86 | end 87 | total = total + 1 88 | if math.fmod(total, 500) == 0 then 89 | fiber.yield() 90 | end 91 | end 92 | 93 | return total_deleted 94 | end 95 | 96 | function model.delete_by_refresh(refresh_token) 97 | if validator.not_empty_string(refresh_token) then 98 | return model.get_space().index[model.REFRESH_INDEX]:delete(refresh_token) 99 | end 100 | end 101 | 102 | function model.get_by_consumer_key(consumer_key, resource_owner) 103 | if validator.not_empty_string(consumer_key) then 104 | local query = {[model.CONSUMER_INDEX_CONSUMER_KEY] = consumer_key} 105 | if validator.not_empty_string(resource_owner) then 106 | query[model.CONSUMER_INDEX_RESOURCE_OWNER] = resource_owner 107 | end 108 | return model.get_space().index[model.CONSUMER_INDEX]:select(query) 109 | end 110 | end 111 | 112 | function model.delete_by_consumer_key(consumer_key, resource_owner) 113 | local token_list = model.get_by_consumer_key(consumer_key, resource_owner) 114 | if token_list ~= nil then 115 | for i, tuple in ipairs(token_list) do 116 | model.get_space():delete({tuple[model.ACCESS_TOKEN]}) 117 | end 118 | return token_list 119 | end 120 | end 121 | 122 | return model 123 | end 124 | 125 | return token 126 | -------------------------------------------------------------------------------- /authman/model/password.lua: -------------------------------------------------------------------------------- 1 | local password = {} 2 | 3 | local digest = require('digest') 4 | local uuid = require('uuid') 5 | local validator = require('authman.validator') 6 | local utils = require('authman.utils.utils') 7 | 8 | local CHAR_GROUP_PATTERNS = { 9 | '[%l]', -- lower case 10 | '[%u]', -- upper case 11 | '[%d]', -- didgets 12 | '[!@#&_=;:,/\\|`~ %?%+%-%.%^%%%$%*]', -- ! @ # & _ = ; : , / \ | ` ~ ? + - . ^ % $ * 13 | } 14 | 15 | local STRENGTH = { 16 | none = { 17 | min_len = 0, 18 | min_group = 0, 19 | }, 20 | whocares = { 21 | min_len = 2, 22 | min_group = 1, 23 | }, 24 | easy = { 25 | min_len = 6, 26 | min_group = 2, 27 | }, 28 | common = { -- default pattern 29 | min_len = 8, 30 | min_group = 2, 31 | }, 32 | moderate = { 33 | min_len = 12, 34 | min_group = 3, 35 | }, 36 | violence = { 37 | min_len = 16, 38 | min_group = 4, 39 | }, 40 | nightmare = { 41 | min_len = 24, 42 | min_group = 4, 43 | }, 44 | } 45 | 46 | ----- 47 | -- password (id, user_id, password) 48 | ----- 49 | function password.model(config) 50 | local model = {} 51 | 52 | model.SPACE_NAME = config.spaces.password.name 53 | 54 | model.PRIMARY_INDEX = 'primary' 55 | model.USER_ID_INDEX = 'user' 56 | 57 | model.ID = 1 58 | model.USER_ID = 2 59 | model.HASH = 3 60 | 61 | function model.get_space() 62 | return box.space[model.SPACE_NAME] 63 | end 64 | 65 | function model.get_by_id(id) 66 | return model.get_space():get(id) 67 | end 68 | 69 | function model.get_by_user_id(user_id) 70 | if validator.not_empty_string(user_id) then 71 | -- TODO create index and migrations 72 | return model.get_space().index[model.USER_ID_INDEX]:select({user_id})[1] 73 | end 74 | end 75 | 76 | function model.delete_by_user_id(user_id) 77 | if validator.not_empty_string(user_id) then 78 | return model.get_space().index[model.USER_ID_INDEX]:delete({user_id}) 79 | end 80 | end 81 | 82 | function model.hash(password, salt) 83 | return utils.salted_hash(password, salt) 84 | end 85 | 86 | function model.is_valid(raw_password, user_id) 87 | local password_tuple = model.get_by_user_id(user_id) 88 | if password_tuple == nil then 89 | return false 90 | end 91 | 92 | return password_tuple[model.HASH] == model.hash(raw_password, user_id) 93 | end 94 | 95 | function model.create(password_tuple) 96 | local id = uuid.str() 97 | return model.get_space():insert({ 98 | id, 99 | password_tuple[model.USER_ID], 100 | password_tuple[model.HASH], 101 | }) 102 | end 103 | 104 | function model.update(password_tuple) 105 | local social_id, fields 106 | social_id = password_tuple[model.ID] 107 | fields = utils.format_update(password_tuple) 108 | return model.get_space():update(social_id, fields) 109 | end 110 | 111 | function model.create_or_update(password_tuple) 112 | local user_id = password_tuple[model.USER_ID] 113 | local exists_password_tuple = model.get_by_user_id(user_id) 114 | if exists_password_tuple == nil then 115 | return model.create(password_tuple) 116 | else 117 | password_tuple[model.ID] = exists_password_tuple[model.ID] 118 | return model.update(password_tuple) 119 | end 120 | end 121 | 122 | function model.strong_enough(password) 123 | local min_len = STRENGTH[config.password_strength]['min_len'] 124 | local min_group = STRENGTH[config.password_strength]['min_group'] 125 | 126 | if min_len ~= nil and string.len(password) < min_len then 127 | return false 128 | end 129 | 130 | if min_group ~= nil then 131 | local char_group_count = 0 132 | for _, pattern in pairs(CHAR_GROUP_PATTERNS) do 133 | if string.match(password, pattern) then 134 | char_group_count = char_group_count + 1 135 | end 136 | end 137 | 138 | if char_group_count < min_group then 139 | return false 140 | end 141 | end 142 | 143 | return true 144 | end 145 | 146 | return model 147 | end 148 | 149 | return password 150 | -------------------------------------------------------------------------------- /authman/model/password_token.lua: -------------------------------------------------------------------------------- 1 | local password_token = {} 2 | 3 | local digest = require('digest') 4 | local validator = require('authman.validator') 5 | 6 | ----- 7 | -- token (user_id, code) 8 | ----- 9 | function password_token.model(config) 10 | local model = {} 11 | 12 | model.SPACE_NAME = config.spaces.password_token.name 13 | 14 | model.PRIMARY_INDEX = 'primary' 15 | 16 | model.USER_ID = 1 17 | model.CODE = 2 18 | 19 | function model.get_space() 20 | return box.space[model.SPACE_NAME] 21 | end 22 | 23 | function model.get_by_user_id(user_id) 24 | return model.get_space():get(user_id) 25 | end 26 | 27 | function model.delete(user_id) 28 | if validator.not_empty_string(user_id) then 29 | return model.get_space():delete({user_id}) 30 | end 31 | end 32 | 33 | function model.generate(user_id) 34 | local token = digest.md5_hex(user_id .. os.time() .. config.restore_secret) 35 | model.get_space():upsert({user_id, token}, {{'=', 2, token}}) 36 | return token 37 | end 38 | 39 | function model.is_valid(user_token, user_id) 40 | local token_tuple = model.get_by_user_id(user_id) 41 | if token_tuple == nil then 42 | return false 43 | end 44 | local token = token_tuple[2] 45 | if token ~= user_token then 46 | return false 47 | else 48 | return true 49 | end 50 | end 51 | 52 | function model.delete(user_id) 53 | return model.get_space():delete(user_id) 54 | end 55 | 56 | return model 57 | end 58 | 59 | return password_token -------------------------------------------------------------------------------- /authman/model/session.lua: -------------------------------------------------------------------------------- 1 | local session = {} 2 | 3 | local utils = require('authman.utils.utils') 4 | local digest = require('digest') 5 | local uuid = require('uuid') 6 | local json = require('json') 7 | 8 | ----- 9 | -- token (id, code, user_id, credential_id(optional)) 10 | ----- 11 | function session.model(config) 12 | local model = {} 13 | 14 | model.SPACE_NAME = config.spaces.session.name 15 | 16 | model.PRIMARY_INDEX = 'primary' 17 | model.USER_ID_INDEX = 'user_index' 18 | 19 | model.ID = 1 20 | model.CODE = 2 21 | model.USER_ID = 3 22 | model.CREDENTIAL_ID = 4 23 | 24 | model.SOCIAL_SESSION_TYPE = 'social' 25 | model.COMMON_SESSION_TYPE = 'common' 26 | 27 | function model.get_space() 28 | return box.space[model.SPACE_NAME] 29 | end 30 | 31 | function model.generate(user_id, credential_id) 32 | local code = uuid.str() 33 | local session_id = uuid.str() 34 | return model.get_space():insert({session_id, code, user_id, credential_id}) 35 | end 36 | 37 | function model.get_by_id(session_id) 38 | return model.get_space():get(session_id) 39 | end 40 | 41 | function model.delete(encoded_session_data) 42 | local session_tuple = model.get_by_session(encoded_session_data) 43 | if session_tuple == nil then 44 | return false 45 | end 46 | 47 | session_tuple = model.get_space():delete(session_tuple[model.ID]) 48 | return session_tuple ~= nil 49 | end 50 | 51 | function model.drop_by_user(user_id) 52 | if user_id == nil then 53 | return nil 54 | end 55 | local user_id = tostring(user_id) 56 | for _, s in model.get_space().index[model.USER_ID_INDEX]:pairs(user_id, {iterator = box.index.EQ}) do 57 | model.get_space():delete(s[model.ID]) 58 | end 59 | end 60 | 61 | function model.decode(encoded_session_data) 62 | local session_data_json, session_data, ok, msg 63 | ok, msg = pcall(function() 64 | session_data_json = digest.base64_decode(encoded_session_data) 65 | session_data = json.decode(session_data_json) 66 | end) 67 | return session_data 68 | end 69 | 70 | function model.get_by_session(encoded_session_data) 71 | local session_data = model.decode(encoded_session_data) 72 | if session_data == nil then 73 | return nil 74 | end 75 | 76 | local session_tuple = model.get_by_id(session_data.sid) 77 | return session_tuple 78 | end 79 | 80 | local function make_session_sign(encoded_session_data, session_code) 81 | local sign = digest.sha256_hex(string.format('%s%s%s', 82 | session_code, 83 | encoded_session_data, 84 | config.session_secret 85 | )) 86 | return utils.base64_encode(sign) 87 | end 88 | 89 | local function get_expiration_time() 90 | return os.time() + config.session_lifetime 91 | end 92 | 93 | local function get_social_update_time() 94 | return os.time() + config.social_check_time 95 | end 96 | 97 | local function split_session(session) 98 | return string.match(session, '([^.]+).([^.]+)') 99 | end 100 | 101 | function model.create(user_id, type, credential_id) 102 | local expiration_time, update_time, session_data, session_tuple 103 | update_time = get_social_update_time() 104 | expiration_time = get_expiration_time() 105 | session_tuple = model.generate(user_id, credential_id) 106 | session_data = { 107 | sid = session_tuple[model.ID], 108 | exp = expiration_time, 109 | type = type, 110 | } 111 | 112 | if type == model.SOCIAL_SESSION_TYPE then 113 | session_data['update'] = update_time 114 | end 115 | 116 | session_data = json.encode(session_data) 117 | local encoded_session_data = utils.base64_encode(session_data) 118 | local encoded_sign = make_session_sign(encoded_session_data, session_tuple[model.CODE]) 119 | return string.format('%s.%s', encoded_session_data, encoded_sign) 120 | end 121 | 122 | function model.validate_session(encoded_session) 123 | local encoded_session_data, session_sign = split_session(encoded_session) 124 | local session_tuple = model.get_by_session(encoded_session_data) 125 | 126 | if session_tuple == nil then 127 | return nil 128 | end 129 | 130 | local sign = make_session_sign(encoded_session_data, session_tuple[model.CODE]) 131 | 132 | if sign ~= session_sign then 133 | return nil 134 | end 135 | 136 | return encoded_session_data 137 | end 138 | 139 | function model.is_expired(session_data) 140 | return session_data.exp <= os.time() 141 | end 142 | 143 | function model.need_common_update(session_data) 144 | return session_data.exp <= (os.time() + config.session_update_timedelta) 145 | end 146 | 147 | function model.need_social_update(session_data) 148 | return session_data.update <= os.time() 149 | end 150 | 151 | return model 152 | end 153 | 154 | return session 155 | -------------------------------------------------------------------------------- /authman/model/social.lua: -------------------------------------------------------------------------------- 1 | local social = {} 2 | 3 | local json = require('json') 4 | local uuid = require('uuid') 5 | local utils = require('authman.utils.utils') 6 | local validator = require('authman.validator') 7 | 8 | ----- 9 | -- social (id, user_id, social_type, social_id, token) 10 | ----- 11 | function social.model(config) 12 | local model = {} 13 | local user = require('authman.model.user').model(config) 14 | local http = require('authman.utils.http').api(config) 15 | 16 | model.SPACE_NAME = config.spaces.social.name 17 | 18 | model.PRIMARY_INDEX = 'primary' 19 | model.USER_ID_INDEX = 'user' 20 | model.SOCIAL_INDEX = 'social' 21 | 22 | model.ID = 1 23 | model.USER_ID = 2 24 | model.PROVIDER = 3 25 | model.SOCIAL_ID = 4 26 | model.TOKEN = 5 27 | 28 | model.ALLOWED_PROVIDERS = {'facebook', 'vk', 'google'} 29 | 30 | function model.get_space() 31 | return box.space[model.SPACE_NAME] 32 | end 33 | 34 | function model.serialize(social_tuple) 35 | return { 36 | provider = social_tuple[model.PROVIDER], 37 | social_id = social_tuple[model.SOCIAL_ID], 38 | } 39 | end 40 | 41 | function model.get_by_id(id) 42 | return model.get_space():get(id) 43 | end 44 | 45 | function model.get_by_user_id(user_id, provider) 46 | if validator.not_empty_string(user_id) then 47 | return model.get_space().index[model.USER_ID_INDEX]:select({user_id, provider})[1] 48 | end 49 | end 50 | 51 | function model.get_by_social_id(social_id, provider) 52 | return model.get_space().index[model.SOCIAL_INDEX]:get({social_id, provider}) 53 | end 54 | 55 | function model.delete_by_user_id(user_id) 56 | if validator.not_empty_string(user_id) then 57 | local social_list = model.get_space().index[model.USER_ID_INDEX]:select({user_id}) 58 | for _, social_tuple in ipairs(social_list) do 59 | model.get_space().index[model.USER_ID_INDEX]:delete({ 60 | social_tuple[model.USER_ID], social_tuple[model.PROVIDER] 61 | }) 62 | end 63 | return social_list 64 | end 65 | end 66 | 67 | function model.create(social_tuple) 68 | local id = uuid.str() 69 | return model.get_space():insert({ 70 | id, 71 | social_tuple[model.USER_ID], 72 | social_tuple[model.PROVIDER], 73 | social_tuple[model.SOCIAL_ID], 74 | social_tuple[model.TOKEN] 75 | }) 76 | end 77 | 78 | function model.update(social_tuple) 79 | local social_id, fields 80 | social_id = social_tuple[model.ID] 81 | fields = utils.format_update(social_tuple) 82 | return model.get_space():update(social_id, fields) 83 | end 84 | 85 | function model.get_social_auth_url(provider, state) 86 | local url, params 87 | 88 | if provider == 'facebook' then 89 | url = 'https://www.facebook.com/v2.8/dialog/oauth' 90 | params = '?client_id=${client_id}&redirect_uri=${redirect_uri}&scope=email' 91 | params = utils.format(params, { 92 | client_id = config[provider].client_id, 93 | redirect_uri = config[provider].redirect_uri 94 | }) 95 | elseif provider == 'vk' then 96 | url = 'https://oauth.vk.com/authorize' 97 | params = '?client_id=${client_id}&display=page&redirect_uri=${redirect_uri}&scope=offline,email&response_type=code&v=5.62' 98 | params = utils.format(params, { 99 | client_id = config[provider].client_id, 100 | redirect_uri = config[provider].redirect_uri 101 | }) 102 | elseif provider == 'google' then 103 | url = 'https://accounts.google.com/o/oauth2/v2/auth' 104 | params = '?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=code&scope=email&access_type=offline&prompt=consent' 105 | params = utils.format(params, { 106 | client_id = config[provider].client_id, 107 | redirect_uri = config[provider].redirect_uri 108 | }) 109 | end 110 | 111 | if validator.not_empty_string(state) then 112 | params = params .. '&state=' .. state 113 | end 114 | 115 | return url .. params 116 | end 117 | 118 | function model.get_token(provider, code, user_tuple) 119 | local response, data, token 120 | if provider == 'facebook' then 121 | response = http.request( 122 | 'GET', 123 | 'https://graph.facebook.com/v2.8/oauth/access_token', 124 | '?client_id=${client_id}&redirect_uri=${redirect_uri}&client_secret=${client_secret}&code=${code}', 125 | { 126 | client_id = config[provider].client_id, 127 | redirect_uri = config[provider].redirect_uri, 128 | client_secret = config[provider].client_secret, 129 | code = code, 130 | } 131 | ) 132 | if response == nil or response.status ~= 200 then 133 | return nil 134 | else 135 | data = json.decode(response.body) 136 | return data.access_token 137 | end 138 | 139 | elseif provider == 'vk' then 140 | response = http.request( 141 | 'GET', 142 | 'https://oauth.vk.com/access_token', 143 | '?client_id=${client_id}&redirect_uri=${redirect_uri}&client_secret=${client_secret}&code=${code}', 144 | { 145 | client_id = config[provider].client_id, 146 | redirect_uri = config[provider].redirect_uri, 147 | client_secret = config[provider].client_secret, 148 | code = code, 149 | } 150 | ) 151 | 152 | if response == nil or response.status ~= 200 then 153 | return nil 154 | else 155 | data = json.decode(response.body) 156 | user_tuple[user.EMAIL] = data.email 157 | return data.access_token 158 | end 159 | 160 | elseif provider == 'google' then 161 | response = http.request( 162 | 'POST', 163 | 'https://www.googleapis.com/oauth2/v4/token', 164 | 'client_id=${client_id}&redirect_uri=${redirect_uri}&client_secret=${client_secret}&code=${code}&grant_type=authorization_code', 165 | { 166 | client_id = config[provider].client_id, 167 | redirect_uri = config[provider].redirect_uri, 168 | client_secret = config[provider].client_secret, 169 | code = code, 170 | } 171 | ) 172 | 173 | if response == nil or response.status ~= 200 then 174 | return nil 175 | else 176 | data = json.decode(response.body) 177 | return data.refresh_token 178 | end 179 | 180 | end 181 | end 182 | 183 | function model.get_profile_info(provider, token, user_tuple) 184 | local url, params, response, data, body, access_token, social_id 185 | user_tuple[user.PROFILE] = {} 186 | 187 | if provider == 'facebook' then 188 | response = http.request( 189 | 'GET', 190 | 'https://graph.facebook.com/me', 191 | '?access_token=${token}&fields=email,first_name,last_name', 192 | { token = token } 193 | ) 194 | if response == nil or response.status ~= 200 then 195 | return nil 196 | else 197 | data = json.decode(response.body) 198 | user_tuple[user.EMAIL] = data.email 199 | user_tuple[user.PROFILE][user.PROFILE_FIRST_NAME] = data.first_name 200 | user_tuple[user.PROFILE][user.PROFILE_LAST_NAME] = data.last_name 201 | return data.id 202 | end 203 | elseif provider == 'vk' then 204 | response = http.request( 205 | 'GET', 206 | 'https://api.vk.com/method/users.get', 207 | '?access_token=${token}&fields=first_name,last_name&v=5.0', 208 | { token = token } 209 | ) 210 | 211 | if response == nil or response.status ~= 200 then 212 | return nil 213 | else 214 | data = json.decode(response.body) 215 | if not validator.table(data.response) then 216 | return nil 217 | end 218 | data = data.response[1] 219 | if data == nil then 220 | return nil 221 | end 222 | if data.id == nil then 223 | return nil 224 | end 225 | user_tuple[user.PROFILE][user.PROFILE_FIRST_NAME] = data.first_name 226 | user_tuple[user.PROFILE][user.PROFILE_LAST_NAME] = data.last_name 227 | return tostring(data.id) 228 | end 229 | 230 | elseif provider == 'google' then 231 | response = http.request( 232 | 'POST', 233 | 'https://www.googleapis.com/oauth2/v4/token', 234 | 'client_id=${client_id}&client_secret=${client_secret}&refresh_token=${token}&grant_type=refresh_token', 235 | { 236 | client_id = config[provider].client_id, 237 | client_secret = config[provider].client_secret, 238 | token = token 239 | } 240 | ) 241 | 242 | if response == nil or response.status ~= 200 then 243 | return nil 244 | end 245 | 246 | data = json.decode(response.body) 247 | access_token = data.access_token 248 | 249 | response = http.request( 250 | 'GET', 251 | 'https://www.googleapis.com/oauth2/v2/userinfo', 252 | '?access_token=${access_token}', 253 | { access_token = access_token } 254 | ) 255 | 256 | if response == nil or response.status ~= 200 then 257 | return nil 258 | end 259 | 260 | data = json.decode(response.body) 261 | 262 | user_tuple[user.EMAIL] = data.email 263 | user_tuple[user.PROFILE][user.PROFILE_FIRST_NAME] = data.given_name 264 | user_tuple[user.PROFILE][user.PROFILE_LAST_NAME] = data.family_name 265 | return data.id 266 | end 267 | end 268 | 269 | return model 270 | end 271 | 272 | return social -------------------------------------------------------------------------------- /authman/model/user.lua: -------------------------------------------------------------------------------- 1 | local user = {} 2 | 3 | local digest = require('digest') 4 | local uuid = require('uuid') 5 | local validator = require('authman.validator') 6 | local utils = require('authman.utils.utils') 7 | 8 | ----- 9 | -- user (uuid, email, type, is_active, profile) 10 | ----- 11 | function user.model(config) 12 | local model = {} 13 | model.SPACE_NAME = config.spaces.user.name 14 | 15 | model.PRIMARY_INDEX = 'primary' 16 | model.EMAIL_INDEX = 'email_index' 17 | 18 | model.ID = 1 19 | model.EMAIL = 2 20 | model.TYPE = 3 21 | model.IS_ACTIVE = 4 22 | model.PROFILE = 5 23 | model.REGISTRATION_TS = 6 -- date of auth.registration or auth.complete_registration 24 | model.SESSION_UPDATE_TS = 7 -- date of auth.auth, auth.social_auth or auth.check_auth if session was updated 25 | 26 | model.PROFILE_FIRST_NAME = 'first_name' 27 | model.PROFILE_LAST_NAME = 'last_name' 28 | 29 | model.COMMON_TYPE = 1 30 | model.SOCIAL_TYPE = 2 31 | 32 | function model.get_space() 33 | return box.space[model.SPACE_NAME] 34 | end 35 | 36 | function model.serialize(user_tuple, data) 37 | 38 | local user_data = { 39 | id = user_tuple[model.ID], 40 | email = user_tuple[model.EMAIL], 41 | is_active = user_tuple[model.IS_ACTIVE], 42 | profile = user_tuple[model.PROFILE], 43 | } 44 | if data ~= nil then 45 | for k,v in pairs(data) do 46 | user_data[k] = v 47 | end 48 | end 49 | 50 | return user_data 51 | end 52 | 53 | function model.get_by_id(user_id) 54 | return model.get_space():get(user_id) 55 | end 56 | 57 | function model.get_by_email(email, type) 58 | if validator.not_empty_string(email) then 59 | return model.get_space().index[model.EMAIL_INDEX]:select({email, type})[1] 60 | end 61 | end 62 | 63 | function model.get_id_by_email(email, type) 64 | local user_tuple = model.get_by_email(email, type) 65 | if user_tuple ~= nil then 66 | return user_tuple[model.ID] 67 | end 68 | end 69 | 70 | function model.delete(user_id) 71 | if validator.not_empty_string(user_id) then 72 | return model.get_space():delete({user_id}) 73 | end 74 | end 75 | 76 | function model.create(user_tuple) 77 | -- create is registration 78 | user_tuple[model.REGISTRATION_TS] = utils.now() 79 | 80 | local user_id 81 | if user_tuple[model.ID] then 82 | user_id = user_tuple[model.ID] 83 | else 84 | user_id = uuid.str() 85 | end 86 | local email = validator.string(user_tuple[model.EMAIL]) and user_tuple[model.EMAIL] or '' 87 | return model.get_space():insert{ 88 | user_id, 89 | email, 90 | user_tuple[model.TYPE], 91 | user_tuple[model.IS_ACTIVE], 92 | user_tuple[model.PROFILE], 93 | user_tuple[model.REGISTRATION_TS], 94 | user_tuple[model.SESSION_UPDATE_TS], 95 | } 96 | end 97 | 98 | function model.update(user_tuple) 99 | local user_id, fields 100 | user_id = user_tuple[model.ID] 101 | fields = utils.format_update(user_tuple) 102 | return model.get_space():update(user_id, fields) 103 | end 104 | 105 | function model.create_or_update(user_tuple) 106 | local user_id = user_tuple[model.ID] 107 | 108 | if user_id and model.get_by_id(user_id) then 109 | user_tuple = model.update(user_tuple) 110 | else 111 | user_tuple = model.create(user_tuple) 112 | end 113 | return user_tuple 114 | end 115 | 116 | function model.set_new_id(user_id, new_user_id) 117 | local user_tuple = model.get_by_id(user_id) 118 | model.delete(user_id) 119 | 120 | model.create_or_update() 121 | return model.get_space():update(user_id, {{'=', model.ID, new_user_id}}) 122 | end 123 | 124 | function model.generate_activation_code(user_id) 125 | return digest.md5_hex(string.format('%s%s', config.activation_secret, user_id)) 126 | end 127 | 128 | function model.update_session_ts(user_tuple) 129 | if not validator.table(user_tuple) then 130 | user_tuple = user_tuple:totable() 131 | end 132 | user_tuple[model.SESSION_UPDATE_TS] = utils.now() 133 | model.update(user_tuple) 134 | end 135 | 136 | return model 137 | end 138 | 139 | return user 140 | -------------------------------------------------------------------------------- /authman/oauth/oauth.lua: -------------------------------------------------------------------------------- 1 | local response = require('authman.response') 2 | local error = require('authman.error') 3 | local validator = require('authman.validator') 4 | 5 | return function(config) 6 | local api = {} 7 | 8 | local user = require('authman.model.user').model(config) 9 | local oauth_app = require('authman.model.oauth.app').model(config) 10 | local oauth_consumer = require('authman.model.oauth.consumer').model(config) 11 | local oauth_code = require('authman.model.oauth.code').model(config) 12 | local oauth_token = require('authman.model.oauth.token').model(config) 13 | local oauth_scope = require('authman.model.oauth.consumer.scope').model(config) 14 | local oauth_redirect = require('authman.model.oauth.consumer.redirect').model(config) 15 | 16 | function api.add_app(user_id, app_name, app_type, redirect_urls, is_trusted) 17 | 18 | local user_tuple = user.get_by_id(user_id) 19 | if user_tuple == nil then 20 | return response.error(error.USER_NOT_FOUND) 21 | end 22 | 23 | if not user_tuple[user.IS_ACTIVE] then 24 | return response.error(error.USER_NOT_ACTIVE) 25 | end 26 | 27 | if not validator.not_empty_string(app_name) 28 | or not validator.oauth_app_type(app_type) 29 | or not validator.not_empty_string(redirect_urls) 30 | then 31 | return response.error(error.INVALID_PARAMS) 32 | end 33 | 34 | local user_apps = oauth_app.get_by_user_id(user_id) 35 | if user_apps ~= nil and #user_apps ~= 0 then 36 | if #user_apps >= config.oauth_max_apps then 37 | return response.error(error.OAUTH_MAX_APPS_REACHED) 38 | end 39 | 40 | for _, app_tuple in pairs(user_apps) do 41 | if app_tuple[oauth_app.NAME] == app_name then 42 | return response.error(error.OAUTH_APP_ALREADY_EXISTS) 43 | end 44 | end 45 | end 46 | 47 | local app_tuple = { 48 | [oauth_app.USER_ID] = user_id, 49 | [oauth_app.NAME] = app_name, 50 | [oauth_app.TYPE] = app_type, 51 | [oauth_app.IS_ACTIVE] = true, 52 | [oauth_app.IS_TRUSTED] = is_trusted or false, 53 | } 54 | 55 | local app = oauth_app.create(app_tuple) 56 | local consumer_secret = oauth_consumer.generate_consumer_secret() 57 | 58 | local consumer = oauth_consumer.create( 59 | oauth_consumer.generate_consumer_key(), 60 | consumer_secret, 61 | app[oauth_app.ID], 62 | redirect_urls 63 | ) 64 | 65 | return response.ok( 66 | oauth_app.serialize( app, { 67 | consumer_key = consumer[oauth_consumer.ID], 68 | consumer_secret = consumer_secret, 69 | redirect_urls = consumer[oauth_consumer.REDIRECT_URLS], 70 | }) 71 | ) 72 | end 73 | 74 | function api.disable_app(app_id) 75 | if not validator.not_empty_string(app_id) then 76 | return response.error(error.INVALID_PARAMS) 77 | end 78 | 79 | local app = oauth_app.update({ 80 | [oauth_app.ID] = app_id, 81 | [oauth_app.IS_ACTIVE] = false, 82 | }) 83 | 84 | if app == nil then 85 | return response.error(error.OAUTH_APP_NOT_FOUND) 86 | end 87 | 88 | return response.ok(oauth_app.serialize(app)) 89 | end 90 | 91 | function api.enable_app(app_id) 92 | if not validator.not_empty_string(app_id) then 93 | return response.error(error.INVALID_PARAMS) 94 | end 95 | 96 | local app = oauth_app.update({ 97 | [oauth_app.ID] = app_id, 98 | [oauth_app.IS_ACTIVE] = true, 99 | }) 100 | 101 | if app == nil then 102 | return response.error(error.OAUTH_APP_NOT_FOUND) 103 | end 104 | 105 | return response.ok(oauth_app.serialize(app)) 106 | end 107 | 108 | function api.delete_app(app_id) 109 | if not validator.not_empty_string(app_id) then 110 | return response.error(error.INVALID_PARAMS) 111 | end 112 | 113 | local consumer = oauth_consumer.delete_by_app_id(app_id) 114 | 115 | if consumer ~= nil then 116 | oauth_code.delete_by_consumer_key(consumer[oauth_consumer.ID]) 117 | oauth_token.delete_by_consumer_key(consumer[oauth_consumer.ID]) 118 | oauth_scope.delete_by_consumer_key(consumer[oauth_consumer.ID]) 119 | oauth_redirect.delete_by_consumer_key(consumer[oauth_consumer.ID]) 120 | end 121 | 122 | local app = oauth_app.delete(app_id) 123 | if app == nil then 124 | return response.error(error.OAUTH_APP_NOT_FOUND) 125 | end 126 | 127 | return response.ok(oauth_app.serialize(app, oauth_consumer.serialize(consumer))) 128 | end 129 | 130 | function api.get_app(app_id) 131 | if not validator.not_empty_string(app_id) then 132 | return response.error(error.INVALID_PARAMS) 133 | end 134 | 135 | local app = oauth_app.get_by_id(app_id) 136 | if app == nil then 137 | return response.error(error.OAUTH_APP_NOT_FOUND) 138 | end 139 | 140 | local consumer = oauth_consumer.get_by_app_id(app_id) 141 | if consumer == nil then 142 | return response.error(error.OAUTH_CONSUMER_NOT_FOUND) 143 | end 144 | 145 | return response.ok(oauth_app.serialize(app, oauth_consumer.serialize(consumer))) 146 | end 147 | 148 | function api.get_user_apps(user_id) 149 | if not validator.not_empty_string(user_id) then 150 | return response.error(error.INVALID_PARAMS) 151 | end 152 | 153 | local result = {} 154 | 155 | local user_apps = oauth_app.get_by_user_id(user_id) 156 | if user_apps ~= nil and #user_apps ~= 0 then 157 | for i, app in pairs(user_apps) do 158 | local consumer = oauth_consumer.get_by_app_id(app[oauth_app.ID]) 159 | result[i] = oauth_app.serialize(app, oauth_consumer.serialize(consumer)) 160 | end 161 | end 162 | 163 | return response.ok(result) 164 | end 165 | 166 | function api.list_apps(offset, limit) 167 | 168 | if not validator.positive_number(offset) then 169 | offset = 0 170 | end 171 | 172 | if not validator.positive_number(limit) then 173 | limit = oauth_app.DEFAULT_LIST_LIMIT 174 | end 175 | 176 | local apps = oauth_app.list(offset, limit) 177 | local total = oauth_app.count_total() 178 | 179 | return response.ok({ data = apps, pager = { total = total, offset = offset, limit = limit }}) 180 | end 181 | 182 | function api.load_consumers(args) 183 | return response.ok(oauth_app.load_by_consumer_keys(args)) 184 | end 185 | 186 | function api.get_consumer(consumer_key) 187 | if not validator.not_empty_string(consumer_key) then 188 | return response.error(error.INVALID_PARAMS) 189 | end 190 | 191 | local consumer = oauth_consumer.get_by_id(consumer_key) 192 | if consumer == nil then 193 | return response.error(error.OAUTH_CONSUMER_NOT_FOUND) 194 | end 195 | 196 | local app = oauth_app.get_by_id(consumer[oauth_consumer.APP_ID]) 197 | if app == nil then 198 | return response.error(error.OAUTH_APP_NOT_FOUND) 199 | end 200 | return response.ok(oauth_app.serialize(app, oauth_consumer.serialize(consumer))) 201 | end 202 | 203 | function api.reset_consumer_secret(consumer_key) 204 | if not validator.not_empty_string(consumer_key) then 205 | return response.error(error.INVALID_PARAMS) 206 | end 207 | 208 | local consumer = oauth_consumer.get_by_id(consumer_key) 209 | 210 | if consumer == nil then 211 | return response.error(error.OAUTH_CONSUMER_NOT_FOUND) 212 | end 213 | 214 | local consumer_secret = oauth_consumer.generate_consumer_secret() 215 | oauth_consumer.update_consumer_secret(consumer_key, consumer_secret, consumer[oauth_consumer.APP_ID]) 216 | 217 | return response.ok(consumer_secret) 218 | end 219 | 220 | function api.save_code(code, consumer_key, redirect_url, scope, state, expires_in, created_at, code_challenge, code_challenge_method, resource_owner) 221 | 222 | local code_tuple = { 223 | [oauth_code.CODE] = code, 224 | [oauth_code.CONSUMER_KEY] = consumer_key, 225 | [oauth_code.REDIRECT_URL] = redirect_url, 226 | [oauth_code.SCOPE] = scope, 227 | [oauth_code.STATE] = state, 228 | [oauth_code.EXPIRES_IN] = expires_in, 229 | [oauth_code.CREATED_AT] = created_at, 230 | [oauth_code.CODE_CHALLENGE] = code_challenge, 231 | [oauth_code.CODE_CHALLENGE_METHOD] = code_challenge_method, 232 | [oauth_code.RESOURCE_OWNER] = resource_owner or "", 233 | } 234 | 235 | for _, field in pairs({oauth_code.CODE, oauth_code.CONSUMER_KEY, oauth_code.REDIRECT_URL, oauth_code.SCOPE}) do 236 | if not validator.not_empty_string(code_tuple[field]) then 237 | return response.error(error.INVALID_PARAMS) 238 | end 239 | end 240 | 241 | for _, field in pairs({oauth_code.EXPIRES_IN, oauth_code.CREATED_AT}) do 242 | if not validator.positive_number(code_tuple[field]) then 243 | return response.error(error.INVALID_PARAMS) 244 | end 245 | end 246 | 247 | return response.ok(oauth_code.serialize(oauth_code.create(code_tuple))) 248 | end 249 | 250 | function api.delete_code(code) 251 | if not validator.not_empty_string(code) then 252 | return response.error(error.INVALID_PARAMS) 253 | end 254 | 255 | local code_tuple = oauth_code.delete(code) 256 | 257 | if code_tuple == nil then 258 | return response.error(error.OAUTH_CODE_NOT_FOUND) 259 | else 260 | return response.ok(oauth_code.serialize(code_tuple)) 261 | end 262 | end 263 | 264 | function api.get_code(code) 265 | if not validator.not_empty_string(code) then 266 | return response.error(error.INVALID_PARAMS) 267 | end 268 | 269 | local code_tuple = oauth_code.get_by_code(code) 270 | 271 | if code_tuple == nil then 272 | return response.error(error.OAUTH_CODE_NOT_FOUND) 273 | end 274 | 275 | local ok, consumer = api.get_consumer(code_tuple[oauth_code.CONSUMER_KEY]) 276 | 277 | -- could not get oauth consumer 278 | -- return error 279 | if not ok then 280 | return ok, consumer 281 | end 282 | 283 | return response.ok(oauth_code.serialize(code_tuple, {consumer = consumer})) 284 | end 285 | 286 | function api.delete_expired_codes(expiration_ts) 287 | if not validator.positive_number(expiration_ts) then 288 | return response.error(error.INVALID_PARAMS) 289 | end 290 | return response.ok(oauth_code.delete_expired(expiration_ts)) 291 | end 292 | 293 | function api.delete_expired_tokens(expiration_ts) 294 | if not validator.positive_number(expiration_ts) then 295 | return response.error(error.INVALID_PARAMS) 296 | end 297 | return response.ok(oauth_token.delete_expired(expiration_ts)) 298 | end 299 | 300 | function api.save_access(access_token, consumer_key, refresh_token, redirect_url, scope, expires_in, created_at, resource_owner) 301 | 302 | local token_tuple = { 303 | [oauth_token.ACCESS_TOKEN] = access_token, 304 | [oauth_token.CONSUMER_KEY] = consumer_key, 305 | [oauth_token.REFRESH_TOKEN] = refresh_token, 306 | [oauth_token.REDIRECT_URL] = redirect_url, 307 | [oauth_token.SCOPE] = scope, 308 | [oauth_token.EXPIRES_IN] = expires_in, 309 | [oauth_token.CREATED_AT] = created_at, 310 | [oauth_token.RESOURCE_OWNER] = resource_owner or "", 311 | } 312 | 313 | for _, field in pairs({oauth_token.ACCESS_TOKEN, oauth_token.CONSUMER_KEY, 314 | oauth_token.REFRESH_TOKEN, oauth_token.REDIRECT_URL, oauth_token.SCOPE}) do 315 | 316 | if not validator.not_empty_string(token_tuple[field]) then 317 | return response.error(error.INVALID_PARAMS) 318 | end 319 | end 320 | 321 | for _, field in pairs({oauth_token.EXPIRES_IN, oauth_token.CREATED_AT}) do 322 | if not validator.positive_number(token_tuple[field]) then 323 | return response.error(error.INVALID_PARAMS) 324 | end 325 | end 326 | 327 | return response.ok(oauth_token.serialize(oauth_token.create(token_tuple))) 328 | end 329 | 330 | function api.delete_access(access_token) 331 | if not validator.not_empty_string(access_token) then 332 | return response.error(error.INVALID_PARAMS) 333 | end 334 | 335 | local token_tuple = oauth_token.delete(access_token) 336 | 337 | if token_tuple == nil then 338 | return response.error(error.OAUTH_ACCESS_TOKEN_NOT_FOUND) 339 | else 340 | return response.ok(oauth_token.serialize(token_tuple)) 341 | end 342 | end 343 | 344 | function api.get_access(access_token) 345 | 346 | if not validator.not_empty_string(access_token) then 347 | return response.error(error.INVALID_PARAMS) 348 | end 349 | 350 | local token_tuple = oauth_token.get_by_access_token(access_token) 351 | 352 | if token_tuple == nil then 353 | return response.error(error.OAUTH_ACCESS_TOKEN_NOT_FOUND) 354 | end 355 | 356 | local ok, consumer = api.get_consumer(token_tuple[oauth_token.CONSUMER_KEY]) 357 | 358 | -- could not get oauth consumer 359 | -- return error 360 | if not ok then 361 | return ok, consumer 362 | end 363 | 364 | return response.ok(oauth_token.serialize(token_tuple, {consumer = consumer})) 365 | end 366 | 367 | function api.get_refresh(refresh_token) 368 | 369 | if not validator.not_empty_string(refresh_token) then 370 | return response.error(error.INVALID_PARAMS) 371 | end 372 | 373 | local token_tuple = oauth_token.get_by_refresh_token(refresh_token) 374 | 375 | if token_tuple == nil then 376 | return response.error(error.OAUTH_ACCESS_TOKEN_NOT_FOUND) 377 | end 378 | 379 | local ok, consumer = api.get_consumer(token_tuple[oauth_token.CONSUMER_KEY]) 380 | 381 | -- could not get oauth consumer 382 | -- return error 383 | if not ok then 384 | return ok, nil 385 | end 386 | 387 | return response.ok(oauth_token.serialize(token_tuple, {consumer = consumer})) 388 | end 389 | 390 | function api.delete_refresh(refresh_token) 391 | if not validator.not_empty_string(refresh_token) then 392 | return response.error(error.INVALID_PARAMS) 393 | end 394 | 395 | local token_tuple = oauth_token.delete_by_refresh(refresh_token) 396 | 397 | if token_tuple == nil then 398 | return response.error(error.OAUTH_ACCESS_TOKEN_NOT_FOUND) 399 | else 400 | return response.ok(oauth_token.serialize(token_tuple)) 401 | end 402 | end 403 | 404 | function api.add_consumer_scopes(consumer_key, user_id, scopes) 405 | 406 | if not validator.not_empty_string(user_id) 407 | or not validator.not_empty_string(consumer_key) 408 | then 409 | return response.error(error.INVALID_PARAMS) 410 | end 411 | 412 | return response.ok(oauth_scope.serialize(oauth_scope.add_consumer_scopes(consumer_key, user_id, scopes))) 413 | end 414 | 415 | function api.get_consumer_authorizations(consumer_key, user_id) 416 | if not validator.not_empty_string(consumer_key) then 417 | return response.error(error.INVALID_PARAMS) 418 | end 419 | 420 | return response.ok(oauth_scope.serialize(oauth_scope.get_by_consumer_key(consumer_key, user_id))) 421 | end 422 | 423 | 424 | function api.get_user_authorizations(user_id) 425 | if not validator.not_empty_string(user_id) then 426 | return response.error(error.INVALID_PARAMS) 427 | end 428 | 429 | local scope_tuples = oauth_scope.get_by_user_id(user_id) 430 | 431 | local data = {} 432 | local scopes = {} 433 | 434 | if scope_tuples ~= nil and #scope_tuples ~= 0 then 435 | for i, scope_tuple in pairs(scope_tuples) do 436 | 437 | local ok, consumer = api.get_consumer(scope_tuple[oauth_scope.CONSUMER_KEY]) 438 | 439 | if ok then 440 | scopes[i] = scope_tuple 441 | data[i] = {consumer = consumer} 442 | end 443 | end 444 | end 445 | 446 | return response.ok(oauth_scope.serialize(scopes, data)) 447 | end 448 | 449 | function api.delete_user_authorizations(user_id, consumer_key) 450 | if not validator.not_empty_string(user_id) 451 | or not validator.not_empty_string(consumer_key) then 452 | return response.error(error.INVALID_PARAMS) 453 | end 454 | 455 | oauth_token.delete_by_consumer_key(consumer_key, user_id) 456 | oauth_code.delete_by_consumer_key(consumer_key, user_id) 457 | 458 | return response.ok(oauth_scope.serialize(oauth_scope.delete_by_consumer_key(consumer_key, user_id))) 459 | end 460 | 461 | function api.save_redirect(consumer_key, user_id, redirect_url) 462 | 463 | if not validator.not_empty_string(user_id) 464 | or not validator.not_empty_string(consumer_key) 465 | or not validator.not_empty_string(redirect_url) 466 | then 467 | return response.error(error.INVALID_PARAMS) 468 | end 469 | 470 | local url_tuple = { 471 | [oauth_redirect.CONSUMER_KEY] = consumer_key, 472 | [oauth_redirect.USER_ID] = user_id, 473 | [oauth_redirect.URL] = redirect_url, 474 | } 475 | 476 | return response.ok(oauth_redirect.serialize(oauth_redirect.upsert_redirect(url_tuple))) 477 | end 478 | 479 | function api.get_consumer_redirects(consumer_key, user_id) 480 | if not validator.not_empty_string(consumer_key) then 481 | return response.error(error.INVALID_PARAMS) 482 | end 483 | 484 | local result = {} 485 | local redirects = oauth_redirect.get_by_consumer_key(consumer_key, user_id) 486 | 487 | if redirects ~= nil and #redirects ~= 0 then 488 | for i, redirect in pairs(redirects) do 489 | result[i] = oauth_redirect.serialize(redirect) 490 | end 491 | end 492 | 493 | return response.ok(result) 494 | end 495 | 496 | 497 | function api.get_user_redirects(user_id) 498 | if not validator.not_empty_string(user_id) then 499 | return response.error(error.INVALID_PARAMS) 500 | end 501 | 502 | local result = {} 503 | local redirects = oauth_redirect.get_by_user_id(user_id) 504 | 505 | if redirects ~= nil and #redirects ~= 0 then 506 | for i, redirect in pairs(redirects) do 507 | 508 | local ok, consumer = api.get_consumer(redirect[oauth_redirect.CONSUMER_KEY]) 509 | 510 | if ok then 511 | result[i] = oauth_redirect.serialize(redirect, {consumer = consumer}) 512 | end 513 | end 514 | end 515 | 516 | return response.ok(result) 517 | end 518 | 519 | function api.delete_user_redirects(user_id, consumer_key) 520 | if not validator.not_empty_string(user_id) 521 | or not validator.not_empty_string(consumer_key) then 522 | return response.error(error.INVALID_PARAMS) 523 | end 524 | 525 | local result = {} 526 | local redirects = oauth_redirect.delete_by_consumer_key(consumer_key, user_id) 527 | 528 | if redirects ~= nil and #redirects ~= 0 then 529 | for i, redirect in pairs(redirects) do 530 | result[i] = oauth_redirect.serialize(redirect) 531 | end 532 | end 533 | 534 | return response.ok(result) 535 | end 536 | 537 | return api 538 | end 539 | -------------------------------------------------------------------------------- /authman/response.lua: -------------------------------------------------------------------------------- 1 | local response = {} 2 | local errors = require('authman.error') 3 | 4 | ---- 5 | -- Standart output format 6 | ---- 7 | 8 | function response.error(code) 9 | local message = {[code] = errors.CODES[code]} 10 | return false, message 11 | end 12 | 13 | function response.ok(data) 14 | return true, data 15 | end 16 | 17 | return response -------------------------------------------------------------------------------- /authman/utils/http.lua: -------------------------------------------------------------------------------- 1 | local http = {} 2 | local utils = require('authman.utils.utils') 3 | local curl_http = require('http.client') 4 | 5 | function http.api(config) 6 | local api = {} 7 | 8 | local timeout = config.request_timeout 9 | 10 | function api.request(method, url, params, param_values) 11 | local response, connection_timeot, read_timeout, body, ok, msg 12 | 13 | if method == 'POST' then 14 | body = utils.format(params, param_values) 15 | ok, msg = pcall(function() 16 | response = curl_http.post(url, body, { 17 | headers = {['Content-Type'] = 'application/x-www-form-urlencoded'}, 18 | timeout = timeout 19 | }) 20 | end) 21 | else 22 | params = utils.format(params, param_values) 23 | url = url .. params 24 | ok, msg = pcall(function() 25 | response = curl_http.get(url, { 26 | timeout = timeout 27 | }) 28 | end) 29 | end 30 | return response 31 | end 32 | 33 | return api 34 | end 35 | 36 | return http -------------------------------------------------------------------------------- /authman/utils/utils.lua: -------------------------------------------------------------------------------- 1 | local utils = {} 2 | local digest = require('digest') 3 | local fiber = require('fiber') 4 | local math = require('math') 5 | local validator = require('authman.validator') 6 | 7 | function utils.now() 8 | return math.floor(fiber.time()) 9 | end 10 | 11 | function utils.format(string, tab) 12 | return (string:gsub('($%b{})', function(word) return tab[word:sub(3, -2)] or word end)) 13 | end 14 | 15 | function utils.format_update(tuple) 16 | local fields = {} 17 | for number, value in pairs(tuple) do 18 | table.insert(fields, {'=', number, value}) 19 | end 20 | return fields 21 | end 22 | 23 | function utils.dump(o) 24 | if type(o) == 'table' then 25 | local s = '{ ' 26 | for k,v in pairs(o) do 27 | if type(k) ~= 'number' then k = '"'..k..'"' end 28 | s = s .. '['..k..'] = ' .. utils.dump(v) .. ',' 29 | end 30 | return s .. '} ' 31 | else 32 | return tostring(o) 33 | end 34 | end 35 | 36 | function utils.base64_encode(string) 37 | return string.gsub( 38 | digest.base64_encode(string), '\n', '' 39 | ) 40 | end 41 | 42 | function utils.lower(string) 43 | if validator.string(string) then 44 | return string:lower() 45 | end 46 | end 47 | 48 | function utils.gen_random_key(key_len) 49 | return string.hex(digest.urandom(key_len or 10)) 50 | end 51 | 52 | function utils.salted_hash(str, salt) 53 | return digest.sha256(string.format('%s%s', salt, str)) 54 | end 55 | 56 | return utils 57 | -------------------------------------------------------------------------------- /authman/validator.lua: -------------------------------------------------------------------------------- 1 | local validator = {} 2 | 3 | local uuid = require('uuid') 4 | local log = require('log') 5 | 6 | local enabled_providers = { 7 | facebook = true, 8 | google = true, 9 | vk = true 10 | } 11 | 12 | local oauth_app_types = { 13 | server = true, 14 | browser = true, 15 | mobile = true, 16 | native = true, 17 | } 18 | 19 | local social_required = { 20 | 'client_id', 'client_secret', 'redirect_uri', 21 | } 22 | 23 | local password_strength = { 24 | none = true, 25 | whocares = true, 26 | easy = true, 27 | common = true, 28 | moderate = true, 29 | violence = true, 30 | nightmare = true, 31 | } 32 | 33 | local config_default_values = { 34 | session_lifetime = 7 * 24 * 60 * 60, 35 | session_update_timedelta = 2 * 24 * 60 * 60, 36 | social_check_time = 60 * 60 * 24, 37 | request_timeout = 3, 38 | oauth_max_apps = 10, 39 | } 40 | 41 | local config_default_secrets = { 42 | activation_secret = uuid.str(), 43 | session_secret = uuid.str(), 44 | restore_secret = uuid.str(), 45 | } 46 | 47 | local config_default_space_names = { 48 | password = 'auth_password_credential', 49 | password_token = 'auth_password_token', 50 | session = 'auth_sesssion', 51 | social = 'auth_social_credential', 52 | user = 'auth_user', 53 | oauth_app = 'auth_oauth_app', 54 | oauth_consumer = 'auth_oauth_consumer', 55 | oauth_code = 'auth_oauth_code', 56 | oauth_token = 'auth_oauth_token', 57 | oauth_scope = 'auth_oauth_scope', 58 | oauth_redirect = 'auth_oauth_redirect', 59 | } 60 | 61 | function validator.string(str) 62 | return type(str) == 'string' 63 | end 64 | 65 | function validator.not_empty_string(str) 66 | return validator.string(str) and str ~= '' 67 | end 68 | 69 | function validator.email(email_string) 70 | return validator.not_empty_string(email_string) and email_string:match('([^@]+@[^@]+)') == email_string 71 | end 72 | 73 | function validator.provider(provider) 74 | return enabled_providers[provider] 75 | end 76 | 77 | function validator.positive_number(number) 78 | return type(number) == 'number' and number >= 0 79 | end 80 | 81 | function validator.table(tbl) 82 | return type(tbl) == 'table' 83 | end 84 | 85 | function validator.password(pwd) 86 | return validator.not_empty_string(pwd) 87 | end 88 | 89 | function validator.oauth_app_type(app_type) 90 | return oauth_app_types[app_type] 91 | end 92 | 93 | function validator.config(config) 94 | if not validator.table(config) then 95 | config = {} 96 | log.warn('Config is not a table. Use default instead.') 97 | end 98 | 99 | if not (validator.not_empty_string(config.password_strength) 100 | or password_strength[config.password_strength]) then 101 | 102 | config.password_strength = 'common' 103 | log.warn('Use common for password_strength') 104 | end 105 | 106 | local param_name, param_value, is_valid 107 | 108 | for param_name, value in pairs(config_default_values) do 109 | param_value = config[param_name] 110 | if param_value == nil or not validator.positive_number(param_value) then 111 | config[param_name] = value 112 | log.warn('Use %s for %s', value, param_name) 113 | end 114 | end 115 | 116 | for param_name, value in pairs(config_default_secrets) do 117 | param_value = config[param_name] 118 | if param_value == nil or not validator.not_empty_string(param_value) then 119 | config[param_name] = value 120 | log.warn('Use %s for %s', value, param_name) 121 | end 122 | end 123 | 124 | if not validator.table(config.spaces) then 125 | config.spaces = {} 126 | end 127 | 128 | for param_name, value in pairs(config_default_space_names) do 129 | if not (validator.table(config.spaces[param_name]) and 130 | validator.not_empty_string(config.spaces[param_name].name)) then 131 | 132 | config.spaces[param_name] = {} 133 | config.spaces[param_name].name = value 134 | end 135 | end 136 | 137 | for provider, enabled in pairs(enabled_providers) do 138 | param_value = config[provider] 139 | if enabled then 140 | if not validator.table(param_value) then 141 | param_value = {} 142 | log.warn('Use empty for %s', provider) 143 | end 144 | 145 | for field_num = 1, #social_required do 146 | if not validator.not_empty_string(param_value[social_required[field_num]]) then 147 | param_value[social_required[field_num]] = '' 148 | log.warn('Use empty for %s in %s', social_required[field_num], provider) 149 | end 150 | end 151 | end 152 | end 153 | return config 154 | end 155 | 156 | return validator 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /config/config.default.lua: -------------------------------------------------------------------------------- 1 | -- Only for example 2 | -- !!! Do not use this in your project !!! 3 | return { 4 | activation_secret = 'ehbgrTUHIJ7689fyvg', 5 | session_secret = 'aswfWERVefver324efv', 6 | restore_secret = 'ybhinjTRCFYVGUHB5678jh', 7 | session_lifetime = 60 * 60 * 24 * 14, 8 | session_update_timedelta = 60 * 60 * 24 * 7, 9 | social_check_time = 60 * 60 * 24, 10 | request_timeout = 3, 11 | oauth_max_apps = 10, 12 | 13 | password_strength = 'moderate', 14 | 15 | spaces = { 16 | password = { 17 | name = 'auth_password_credential', 18 | }, 19 | password_token = { 20 | name = 'auth_password_token', 21 | }, 22 | session = { 23 | name = 'auth_sesssion', 24 | }, 25 | social = { 26 | name = 'auth_social_credential', 27 | }, 28 | user = { 29 | name = 'auth_user', 30 | }, 31 | oauth_app = { 32 | name = 'auth_oauth_app', 33 | }, 34 | oauth_consumer = { 35 | name = 'auth_oauth_consumer', 36 | }, 37 | oauth_code = { 38 | name = 'auth_oauth_code', 39 | }, 40 | oauth_token = { 41 | name = 'auth_oauth_token', 42 | }, 43 | oauth_scope = { 44 | name = 'auth_oauth_scope', 45 | }, 46 | oauth_redirect = { 47 | name = 'auth_oauth_redirect', 48 | }, 49 | }, 50 | 51 | facebook = { 52 | client_id = '1813232428941062', 53 | client_secret = '3bb5bbe8b72ntyjtyj6ce9d5cbff3b3', 54 | redirect_uri='http://localhost:8000/', 55 | }, 56 | google = { 57 | client_id = '49534024531-3gmtvon6ryjtyjajn5piek6jgi0p2o47.apps.googleusercontent.com', 58 | client_secret = 'aaFDtukyukyu8YqeBWOeAnfGYY', 59 | redirect_uri='http://localhost:8000/', 60 | }, 61 | vk = { 62 | client_id = '54567474475', 63 | client_secret = 'nwUergeyjDR6ToDtX6', 64 | redirect_uri='http://localhost:8000/', 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | tarantool-authman (1.0.0) stable; urgency=medium 2 | 3 | * Initial release 4 | 5 | -- Denis Linnik Thu, 28 Feb 2017 20:00:00 +0300 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: tarantool-authman 2 | Priority: optional 3 | Section: database 4 | Maintainer: Denis Linnik 5 | Build-Depends: debhelper (>= 9) 6 | Standards-Version: 3.9.6 7 | Homepage: https://github.com/mailru/tarantool-authman 8 | Vcs-Git: git:/github.com/mailru/tarantool-authman.git 9 | Vcs-Browser: https://github.com/mailru/tarantool-authman 10 | 11 | Package: tarantool-authman 12 | Architecture: all 13 | Depends: tarantool (>= 1.7.4.168), 14 | ${misc:Depends} 15 | Description: Auth module 16 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Debianized-By: Denis Linnik 3 | Upstream-Name: tarantool-authman 4 | Upstream-Contact: support@tarantool.org 5 | Source: https://github.com/mailru/tarantool-authman 6 | 7 | Files: * 8 | Copyright: 2017 Denis Linnik 9 | License: MIT 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/tarantool-authman.install: -------------------------------------------------------------------------------- 1 | authman/*.lua usr/share/tarantool/authman/ 2 | authman/oauth/*.lua usr/share/tarantool/authman/oauth/ 3 | authman/utils/*.lua usr/share/tarantool/authman/utils/ 4 | authman/model/*.lua usr/share/tarantool/authman/model/ 5 | authman/model/oauth/*.lua usr/share/tarantool/authman/model/oauth/ 6 | authman/model/oauth/consumer/*.lua usr/share/tarantool/authman/model/oauth/consumer/ 7 | authman/migrations/*.lua usr/share/tarantool/authman/migrations/ 8 | 9 | -------------------------------------------------------------------------------- /doc/oauth2.md: -------------------------------------------------------------------------------- 1 | ## OAuth2 related API methods 2 | 3 | #### auth.oauth.add_app(user_id, app_name, app_type, redirect_urls, is_trusted) 4 | ``` 5 | tarantool> ok, app = auth.oauth.add_app('268a8464-e39d-4f82-b55f-e68197c6c3f2', 'test app', 'browser', 'https://example.com/1 https://example.com/2', false) 6 | tarantool> app 7 | - is_active: true 8 | redirect_urls: https://example.com/1 https://example.com/2 9 | consumer_key: 2f2d9591fae84f4fb388bab878ee4613 10 | id: bcf5297f-3192-403d-a316-13d31780160a 11 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 12 | consumer_secret: 41eef0fc10f9a62a1b301a7f1db703e076271b01f148efcdc3dcb3a69a78101c 13 | name: test app 14 | type: browser 15 | is_trusted: false 16 | ``` 17 | Register new OAuth client. Return OAuth client table. Valid application types are "server", "browser", "mobile", "native". 18 | Optional is_trusted parameter indicates that app is approved to be authorized by all resource owners. 19 | 20 | #### auth.oauth.disable_app(app_id) 21 | ``` 22 | tarantool> ok, app = auth.oauth.disable_app('bcf5297f-3192-403d-a316-13d31780160a') 23 | tarantool> app 24 | --- 25 | - is_active: false 26 | id: bcf5297f-3192-403d-a316-13d31780160a 27 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 28 | name: test app 29 | type: browser 30 | ``` 31 | Disable OAuth client. Return app table. 32 | 33 | #### auth.oauth.enable_app(app_id) 34 | ``` 35 | tarantool> ok, app = auth.oauth.enable_app('bcf5297f-3192-403d-a316-13d31780160a') 36 | tarantool> app 37 | --- 38 | - is_active: true 39 | id: bcf5297f-3192-403d-a316-13d31780160a 40 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 41 | name: test app 42 | type: browser 43 | ``` 44 | Enable OAuth client. Return app table. 45 | 46 | #### auth.oauth.delete_app(app_id) 47 | ``` 48 | tarantool> ok, app = auth.oauth.delete_app('bcf5297f-3192-403d-a316-13d31780160a') 49 | tarantool> app 50 | --- 51 | - consumer_key: 2f2d9591fae84f4fb388bab878ee4613 52 | id: bcf5297f-3192-403d-a316-13d31780160a 53 | is_active: true 54 | redirect_urls: https://example.com/1 https://example.com/2 55 | type: browser 56 | consumer_secret_hash: !!binary fhHEsS7eXm/oFUN2yV+eK7KVb9ydn1iQi9/PlZZYjRw= 57 | application_id: bcf5297f-3192-403d-a316-13d31780160a 58 | name: test app 59 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 60 | ``` 61 | Delete OAuth client. Return OAuth client table. 62 | 63 | #### auth.oauth.get_app(app_id) 64 | ``` 65 | tarantool> ok, app = auth.oauth.get_app('1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7') 66 | tarantool> app 67 | --- 68 | - consumer_key: 9169b664839bca439ca11fe4274838b2 69 | id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 70 | is_active: true 71 | redirect_urls: https://example.com/1 https://example.com/2 72 | type: browser 73 | consumer_secret_hash: !!binary xPVbgBDs53gqmhp7ZBtzKIjU9IjtDlQYfqMAQOfVYLU= 74 | application_id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 75 | name: test app 76 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 77 | ``` 78 | Given application id return OAuth client info 79 | 80 | #### auth.oauth.get_user_apps(user_id) 81 | ``` 82 | tarantool> ok, apps = auth.oauth.get_user_apps('268a8464-e39d-4f82-b55f-e68197c6c3f2') 83 | tarantool> apps 84 | - - consumer_key: 9169b664839bca439ca11fe4274838b2 85 | id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 86 | is_active: true 87 | redirect_urls: https://example.com/1 https://example.com/2 88 | type: browser 89 | consumer_secret_hash: !!binary hYVXta3lfUE/f8VctPZcmN4FE7yeRJvqao6YrviHBzs= 90 | application_id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 91 | name: test app 92 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 93 | ``` 94 | Given user id return list of user's applications 95 | 96 | #### auth.oauth.get_consumer(consumer_key) 97 | ``` 98 | tarantool> ok, consumer = auth.oauth.get_consumer('9169b664839bca439ca11fe4274838b2') 99 | tarantool> consumer 100 | --- 101 | - consumer_key: 9169b664839bca439ca11fe4274838b2 102 | id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 103 | is_active: true 104 | redirect_urls: https://example.com/1 https://example.com/2 105 | type: browser 106 | consumer_secret_hash: !!binary xPVbgBDs53gqmhp7ZBtzKIjU9IjtDlQYfqMAQOfVYLU= 107 | application_id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 108 | name: test app 109 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 110 | ... 111 | ``` 112 | Given consumer key return OAuth client info. 113 | 114 | #### auth.oauth.load_consumers({consumer_key1, consumer_key2, ...}) 115 | ``` 116 | tarantool> ok, consumers = auth.oauth.load_consumers({"99908ae23c746b4db8b576f93d02b4d4","f2c53fa65cd6b143aa9c105f37915ce0"}) 117 | tarantool> consumers 118 | --- 119 | - f2c53fa65cd6b143aa9c105f37915ce0: 120 | consumer_key: f2c53fa65cd6b143aa9c105f37915ce0 121 | id: 58b4ca14-1a1a-47be-bfad-beacec7846a6 122 | is_active: true 123 | is_trusted: true 124 | user_id: 7f66aafb-5ab7-4fd5-8b4f-3f07c2c6cea3 125 | type: browser 126 | redirect_urls: https://mediator.media 127 | consumer_secret_hash: !!binary o6PdmhDB5/NNBAEcyZ2nLVn1R/zuHTlIw11qBgrJgHs= 128 | name: test2 129 | app_id: 58b4ca14-1a1a-47be-bfad-beacec7846a6 130 | 99908ae23c746b4db8b576f93d02b4d4: 131 | consumer_key: 99908ae23c746b4db8b576f93d02b4d4 132 | id: 94e385eb-f61b-4c47-9f3c-50e061c0c5fb 133 | is_active: true 134 | is_trusted: true 135 | user_id: 7f66aafb-5ab7-4fd5-8b4f-3f07c2c6cea3 136 | type: server 137 | redirect_urls: http://mail.ru 138 | consumer_secret_hash: !!binary yqQe2Vl1e12Kdh6a1GMbq2D1CRUyOn1OvSXxt57o3/w= 139 | name: test3 140 | app_id: 94e385eb-f61b-4c47-9f3c-50e061c0c5fb 141 | ... 142 | ``` 143 | Given table with consumer keys return table which keys are consumer keys and values are app tuples. 144 | 145 | #### auth.oauth.reset_consumer_secret(consumer_key) 146 | ``` 147 | tarantool> ok, secret = auth.oauth.reset_consumer_secret('9169b664839bca439ca11fe4274838b2') 148 | tarantool> secret 149 | --- 150 | - a6d9ef466f38dec5ea85106da7a419151e83b1df0445a92400845830329e13f9 151 | ``` 152 | Given consumer key generate and save new consumer secret. Return new consumer secret. 153 | 154 | #### auth.oauth.save_code(code, consumer_key, redirect_url, scope, state, expires_in, created_at, code_challenge, code_challenge_method, resource_owner) 155 | ``` 156 | tarantool> ok, code = auth.oauth.save_code('some code', '9169b664839bca439ca11fe4274838b2', 'https://example.com/1', 'read', 'some state', 600, 1514452159, 'code challenge', 'code challenge method', 'user_id or resource owner') 157 | tarantool> code 158 | --- 159 | - expires_in: 600 160 | code: some code 161 | redirect_url: https://example.com/1 162 | created_at: 1514452159 163 | scope: read 164 | code_challenge_method: code challenge method 165 | state: some state 166 | consumer_key: 9169b664839bca439ca11fe4274838b2 167 | code_challenge: code challenge 168 | ``` 169 | Save OAuth authorization code info. Return authorization code table w/o consumer. See rfc7636 for explanation of code_challenge and code_challenge_method params. 170 | 171 | #### auth.oauth.get_code(code) 172 | ``` 173 | tarantool> ok, code = auth.oauth.get_code('some code') 174 | tarantool> code 175 | --- 176 | - expires_in: 600 177 | code: some code 178 | redirect_url: https://example.com/1 179 | created_at: 1514452159 180 | scope: read 181 | consumer: 182 | consumer_key: 9169b664839bca439ca11fe4274838b2 183 | id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 184 | is_active: true 185 | redirect_urls: https://example.com/1 https://example.com/2 186 | type: browser 187 | consumer_secret_hash: !!binary hYVXta3lfUE/f8VctPZcmN4FE7yeRJvqao6YrviHBzs= 188 | application_id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 189 | name: test app 190 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 191 | code_challenge_method: code challenge method 192 | state: some state 193 | consumer_key: 9169b664839bca439ca11fe4274838b2 194 | code_challenge: code challenge 195 | ``` 196 | Return OAuth authorization code info 197 | 198 | #### auth.oauth.delete_code(code) 199 | ``` 200 | tarantool> ok, code = auth.oauth.delete_code('some code') 201 | tarantool> code 202 | - expires_in: 600 203 | code: some code 204 | redirect_url: https://example.com/1 205 | created_at: 1514452159 206 | scope: read 207 | code_challenge_method: code challenge method 208 | state: some state 209 | consumer_key: 9169b664839bca439ca11fe4274838b2 210 | code_challenge: code challenge 211 | ``` 212 | Delete OAuth authorization code. Return authorization code table without consumer. 213 | 214 | #### auth.oauth.delete_expired_codes(expiration_ts) 215 | ``` 216 | tarantool> ok, deleted_cnt = auth.oauth.delete_expired_codes(1514452759) 217 | tarantool> deleted_cnt 218 | --- 219 | - 1 220 | ``` 221 | Given timestamp delete OAuth authorization codes having expiration time lt than this timestamp 222 | 223 | #### auth.oauth.save_access(access_token, consumer_key, refresh_token, redirect_url, scope, expires_in, created_at, resource_owner) 224 | ``` 225 | tarantool> ok, access = auth.oauth.save_access('some token', '9169b664839bca439ca11fe4274838b2', 'some refresh token', 'https://example.com/1', 'read', 3600, 1514452760, 'user_id of resource owner') 226 | tarantool> access 227 | --- 228 | - refresh_token: some refresh token 229 | access_token: some token 230 | consumer_key: 9169b664839bca439ca11fe4274838b2 231 | expires_in: 3600 232 | redirect_url: https://example.com/1 233 | created_at: 1514452760 234 | scope: read 235 | ``` 236 | Save OAuth access token info. 237 | 238 | #### auth.oauth.get_access(access_token) 239 | ``` 240 | tarantool> ok, access = auth.oauth.get_access('some token') 241 | tarantool> access 242 | --- 243 | - refresh_token: some refresh token 244 | access_token: some token 245 | consumer_key: 9169b664839bca439ca11fe4274838b2 246 | consumer: 247 | consumer_key: 9169b664839bca439ca11fe4274838b2 248 | id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 249 | is_active: true 250 | redirect_urls: https://example.com/1 https://example.com/2 251 | type: browser 252 | consumer_secret_hash: !!binary hYVXta3lfUE/f8VctPZcmN4FE7yeRJvqao6YrviHBzs= 253 | application_id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 254 | name: test app 255 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 256 | expires_in: 3600 257 | redirect_url: https://example.com/1 258 | created_at: 1514452760 259 | scope: read 260 | ``` 261 | Return OAuth access token info 262 | 263 | #### auth.oauth.delete_access(access_token) 264 | ``` 265 | tarantool> ok, access = auth.oauth.delete_access('some token') 266 | tarantool> access 267 | --- 268 | - refresh_token: some refresh token 269 | access_token: some token 270 | consumer_key: 9169b664839bca439ca11fe4274838b2 271 | expires_in: 3600 272 | redirect_url: https://example.com/1 273 | created_at: 1514452760 274 | scope: read 275 | ``` 276 | Delete OAuth access token. Return token table without consumer 277 | 278 | #### auth.oauth.delete_expired_token(expiration_ts) 279 | ``` 280 | tarantool> ok, deleted_cnt = auth.oauth.delete_expired_tokens(1514456869) 281 | tarantool> deleted_cnt 282 | --- 283 | - 1 284 | ``` 285 | Given timestamp delete OAuth access tokens having expiration time lt than this timestamp 286 | 287 | #### auth.oauth.get_refresh(refresh_token) 288 | ``` 289 | tarantool> ok, refresh = auth.oauth.get_refresh('some refresh token') 290 | tarantool> refresh 291 | --- 292 | - refresh_token: some refresh token 293 | access_token: some token 294 | consumer_key: 9169b664839bca439ca11fe4274838b2 295 | consumer: 296 | consumer_key: 9169b664839bca439ca11fe4274838b2 297 | id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 298 | is_active: true 299 | redirect_urls: https://example.com/1 https://example.com/2 300 | type: browser 301 | consumer_secret_hash: !!binary hYVXta3lfUE/f8VctPZcmN4FE7yeRJvqao6YrviHBzs= 302 | application_id: 1cd3806a-4221-4ff2-aa7c-e5d076c4c1a7 303 | name: test app 304 | user_id: 268a8464-e39d-4f82-b55f-e68197c6c3f2 305 | expires_in: 3600 306 | redirect_url: https://example.com/1 307 | created_at: 1514452760 308 | scope: read 309 | ``` 310 | Return OAuth refresh token info. 311 | 312 | #### auth.oauth.delete_refresh(access_refresh) 313 | ``` 314 | tarantool> ok, refresh = auth.oauth.delete_refresh('some refresh token') 315 | tarantool> refresh 316 | --- 317 | - refresh_token: some refresh token 318 | access_token: some token 319 | consumer_key: 9169b664839bca439ca11fe4274838b2 320 | expires_in: 3600 321 | redirect_url: https://example.com/1 322 | created_at: 1514452760 323 | scope: read 324 | ``` 325 | Delete OAuth refresh token. Return token table w/o consumer 326 | 327 | #### auth.oauth.add_consumer_scopes(consumer_key, user_id, scopes) 328 | ``` 329 | tarantool> ok, consumer_scopes = auth.oauth.add_consumer_scopes('06b043c219752541ab50e82627148161', '958e93a7-e6ca-471d-836e-21086c399c6d', {'read', 'write'}) 330 | tarantool> consumer_scopes 331 | --- 332 | - - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 333 | consumer_key: 06b043c219752541ab50e82627148161 334 | name: read 335 | - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 336 | consumer_key: 06b043c219752541ab50e82627148161 337 | name: write 338 | ``` 339 | Given consumer_key, user_id and list of scopes extend scopes granted by the user to the consumer. Return a list of scopes granted by the user to the consumer. 340 | 341 | #### auth.oauth.get_user_authorizations(user_id) 342 | ``` 343 | tarantool> ok, user_authorizations = app.auth.oauth.get_user_authorizations('958e93a7-e6ca-471d-836e-21086c399c6d', '06b043c219752541ab50e82627148161') 344 | tarantool> user_authorizations 345 | --- 346 | - - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 347 | consumer_key: 06b043c219752541ab50e82627148161 348 | name: read 349 | - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 350 | consumer_key: 06b043c219752541ab50e82627148161 351 | name: write 352 | ``` 353 | Given user_id return list of scopes granted by the user. 354 | 355 | #### auth.oauth.delete_user_authorizations(user_id, consumer_key) 356 | ``` 357 | tarantool> ok, deleted = app.auth.oauth.delete_user_authorizations('958e93a7-e6ca-471d-836e-21086c399c6d', '06b043c219752541ab50e82627148161') 358 | tarantool> deleted 359 | --- 360 | - - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 361 | consumer_key: 06b043c219752541ab50e82627148161 362 | name: read 363 | - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 364 | consumer_key: 06b043c219752541ab50e82627148161 365 | name: write 366 | ``` 367 | Given user_id and consumer_key delete all scopes granted by the user to the consumer. Return a list of deleted scopes. 368 | 369 | #### auth.oauth.save_redirect(consumer_key, user_id, redirect_url) 370 | ``` 371 | tarantool> ok, data = auth.oauth.save_redirect('06b043c219752541ab50e82627148161', '958e93a7-e6ca-471d-836e-21086c399c6d', 'http://test.ru/test1') 372 | tarantool> data 373 | --- 374 | - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 375 | consumer_key: 06b043c219752541ab50e82627148161 376 | url: http://test.ru/test1 377 | ``` 378 | Add redirect to consumer's redirects list. Return redirect tuple. 379 | 380 | #### auth.oauth.get_user_redirects(user_id) 381 | ``` 382 | tarantool> ok, data = auth.oauth.get_user_redirects('958e93a7-e6ca-471d-836e-21086c399c6d') 383 | tarantool> data 384 | --- 385 | - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 386 | consumer_key: 06b043c219752541ab50e82627148161 387 | url: http://test.ru/test1 388 | consumer: 389 | consumer_key: 06b043c219752541ab50e82627148161 390 | id: 95767152-b7c7-456b-bb21-a13ac17350a7 391 | is_active: true 392 | is_trusted: false 393 | user_id: 7f66aafb-5ab7-4fd5-8b4f-3f07c2c6cea3 394 | type: browser 395 | redirect_urls: https://mediator.media 396 | consumer_secret_hash: !!binary eHrD00klpfupEm7mkyqnX/PYlTy7u5AGEsb2/BIcp44= 397 | name: test5 398 | app_id: 95767152-b7c7-456b-bb21-a13ac17350a7 399 | ``` 400 | Given user_id return list of user's redirects. 401 | 402 | #### auth.oauth.get_consumer_redirects(consumer_key, user_id) 403 | ``` 404 | tarantool> ok, data = auth.oauth.get_consumer_redirects('06b043c219752541ab50e82627148161') 405 | tarantool> data 406 | --- 407 | - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 408 | consumer_key: 06b043c219752541ab50e82627148161 409 | url: http://test.ru/test1 410 | ``` 411 | Given consumer_key and user_id (optional) return list of consumer's redirects. 412 | 413 | #### auth.oauth.delete_user_redirects(user_id, consumer_key) 414 | ``` 415 | tarantool> ok, data = auth.oauth.delete_user_redirects('958e93a7-e6ca-471d-836e-21086c399c6d', '06b043c219752541ab50e82627148161') 416 | tarantool> data 417 | --- 418 | - - user_id: 958e93a7-e6ca-471d-836e-21086c399c6d 419 | consumer_key: 06b043c219752541ab50e82627148161 420 | url: http://test.ru/test1 421 | ``` 422 | Given user_id and consumer_key delete user's redirects for the consumer. Return list of deleted redirects. 423 | 424 | #### auth.oauth.list_apps(offset, limit) 425 | ``` 426 | tarantool> ok, data = auth.oauth.list_apps(0, 2) 427 | tarantool> data 428 | --- 429 | - true 430 | - data: 431 | - consumer_key: 175f0bae674aa64db615b86e162246df 432 | id: 3a32c0e7-137f-436d-8049-f34c4164bb55 433 | user: 434 | is_active: true 435 | email: diez@devmail.ru 436 | profile: null 437 | id: 5c2ec04d-1c0c-4966-a705-f358a9084345 438 | is_active: true 439 | is_trusted: true 440 | user_id: 5c2ec04d-1c0c-4966-a705-f358a9084345 441 | type: browser 442 | redirect_urls: http://test.com/test2 443 | consumer_secret_hash: !!binary 7UeupUFjN58PpFw4fillc3/mTJVbk1nhfphBimJY5PU= 444 | name: test app 2 445 | app_id: 3a32c0e7-137f-436d-8049-f34c4164bb55 446 | - consumer_key: e08ef89d66dea2488ab0189617b88e3d 447 | id: 8b89066f-5603-46ab-8f38-85d4cc18e4ed 448 | user: 449 | is_active: true 450 | email: diez@devmail.ru 451 | profile: null 452 | id: 5c2ec04d-1c0c-4966-a705-f358a9084345 453 | is_active: true 454 | is_trusted: true 455 | user_id: 5c2ec04d-1c0c-4966-a705-f358a9084345 456 | type: browser 457 | redirect_urls: http://test.com/test1 458 | consumer_secret_hash: !!binary 7DCGKXT4MspO8zpMFr66Rq3gFO2G5CZGESzSH/5lqPk= 459 | name: test app 1 460 | app_id: 8b89066f-5603-46ab-8f38-85d4cc18e4ed 461 | pager: 462 | offset: 0 463 | limit: 2 464 | total: 2 465 | ``` 466 | Return table with 2 elements: 467 | - data - list of app tuples 468 | - pager - values for pagination 469 | -------------------------------------------------------------------------------- /rpm/tarantool-authman.spec: -------------------------------------------------------------------------------- 1 | Name: tarantool-authman 2 | Version: 1.0.0 3 | Release: 1 4 | Summary: Tarantool auth module 5 | Group: Applications/Databases 6 | License: BSD 7 | 8 | URL: https://github.com/mailru/tarantool-authman 9 | Source0: https://github.com/mailru/%{name}/archive/%{version}/%{name}-%{version}.tar.gz 10 | 11 | BuildArch: noarch 12 | Requires: tarantool >= 1.7.4.168 13 | 14 | %description 15 | auth lib for tarantool 16 | 17 | 18 | %prep 19 | %setup -q -n %{name}-%{version} 20 | 21 | %define luapkgdir %{_datadir}/tarantool/authman 22 | 23 | %install 24 | rm -rf %{buildroot}/%{name}-%{version} 25 | 26 | %{__mkdir_p} %{buildroot}/%{luapkgdir}/ 27 | cp -pR %{_builddir}/%{name}-%{version}/authman/* %{buildroot}/%{luapkgdir}/ 28 | cp -pR %{_builddir}/%{name}-%{version}/README.md %{buildroot}/%{luapkgdir}/README.md 29 | cp -pR %{_builddir}/%{name}-%{version}/doc/ %{buildroot}/%{luapkgdir}/doc/ 30 | 31 | 32 | %clean 33 | rm -rf %{buildroot} 34 | 35 | 36 | %files 37 | %dir %{luapkgdir} 38 | %dir %{luapkgdir}/model 39 | %dir %{luapkgdir}/utils 40 | %{luapkgdir}/*.lua 41 | %{luapkgdir}/model/*.lua 42 | %{luapkgdir}/model/oauth/*.lua 43 | %{luapkgdir}/model/oauth/consumer/*.lua 44 | %{luapkgdir}/utils/*.lua 45 | %{luapkgdir}/migrations/*.lua 46 | %{luapkgdir}/oauth/*.lua 47 | %doc %{luapkgdir}/README.md 48 | %doc %{luapkgdir}/doc/*.md 49 | -------------------------------------------------------------------------------- /test/authman.test.lua: -------------------------------------------------------------------------------- 1 | local config = require('test.config') 2 | 3 | local test_db_path = config.test_database_dir 4 | 5 | -- mock 6 | package.loaded['authman.utils.http'] = require('test.mock.authman.utils.http') 7 | 8 | os.execute('mkdir -p ' .. test_db_path) 9 | 10 | box.cfg { 11 | listen = config.port, 12 | wal_dir = test_db_path, 13 | memtx_dir = test_db_path, 14 | } 15 | 16 | local TEST_CASES = { 17 | 'test.case.registration', 18 | 'test.case.auth', 19 | 'test.case.restore_password', 20 | 'test.case.profile', 21 | 'test.case.social', 22 | 'test.case.complex', 23 | 'test.case.oauth.app', 24 | 'test.case.oauth.oauth', 25 | 'test.case.oauth.scope', 26 | 'test.case.oauth.redirect', 27 | } 28 | 29 | local function run() 30 | for case_index = 1, #TEST_CASES do 31 | local case = require(TEST_CASES[case_index]) 32 | case.setup() 33 | for test_index = 1, #case.tests do 34 | case.before() 35 | case.tests[test_index]() 36 | case.after() 37 | end 38 | case.teardown() 39 | end 40 | end 41 | 42 | run() 43 | 44 | os.execute('rm -rf '.. test_db_path) 45 | os.exit() 46 | -------------------------------------------------------------------------------- /test/case/auth.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local fiber = require('fiber') 4 | local response = require('authman.response') 5 | local error = require('authman.error') 6 | local validator = require('authman.validator') 7 | local v = require('test.values') 8 | 9 | -- model configuration 10 | local config = validator.config(require('test.config')) 11 | local db = require('authman.db').configurate(config) 12 | local auth = require('authman').api(config) 13 | local user_space = require('authman.model.user').model(config).get_space() 14 | 15 | local test = tap.test('auth_test') 16 | 17 | function exports.setup() end 18 | 19 | function exports.before() 20 | local ok, user 21 | ok, user = auth.registration('test@test.ru') 22 | auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 23 | ok, user = auth.registration('not_active@test.ru') 24 | end 25 | 26 | function exports.after() 27 | db.truncate_spaces() 28 | end 29 | 30 | function exports.teardown() end 31 | 32 | function test_auth_success() 33 | local ok, user,session, expected 34 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 35 | session = user['session'] 36 | user['id'] = nil 37 | user['session'] = nil 38 | expected = {email = 'test@test.ru', is_active = true} 39 | test:is(ok, true, 'test_auth_success user logged in') 40 | test:isstring(session, 'test_auth_success session returned') 41 | test:is_deeply(user, expected, 'test_auth_success user returned') 42 | end 43 | 44 | function test_check_auth_success() 45 | local ok, user,session, expected 46 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 47 | session = user['session'] 48 | ok, user = auth.check_auth(session) 49 | session = user['session'] 50 | user['id'] = nil 51 | user['session'] = nil 52 | expected = {email = 'test@test.ru', is_active = true } 53 | test:is(ok, true, 'test_check_auth_success user logged in') 54 | test:isstring(session, 'test_check_auth_success session returned') 55 | test:is_deeply(user, expected, 'test_check_auth_success user returned') 56 | end 57 | 58 | function test_auth_wrong_password() 59 | local got, expected 60 | got = {auth.auth('test@test.ru', 'wrong_password'), } 61 | expected = {response.error(error.WRONG_PASSWORD), } 62 | test:is_deeply(got, expected, 'test_auth_wrong_password') 63 | end 64 | 65 | function test_auth_user_not_found() 66 | local got, expected 67 | got = {auth.auth('not_found@test.ru', v.USER_PASSWORD), } 68 | expected = {response.error(error.USER_NOT_FOUND), } 69 | test:is_deeply(got, expected, 'test_auth_user_not_found') 70 | end 71 | 72 | function test_auth_user_not_active() 73 | local got, expected 74 | got = {auth.auth('not_active@test.ru', v.USER_PASSWORD), } 75 | expected = {response.error(error.USER_NOT_ACTIVE), } 76 | test:is_deeply(got, expected, 'test_auth_user_not_active') 77 | end 78 | 79 | function test_check_auth_wrong_sign() 80 | local ok, user, got, expected 81 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 82 | got = {auth.check_auth('thissession.iswrongsigned'), } 83 | expected = {response.error(error.WRONG_SESSION_SIGN), } 84 | test:is_deeply(got, expected, 'test_check_auth_wrong_sign') 85 | end 86 | 87 | function test_check_auth_user_not_found() 88 | local ok, user, id, session, got, expected 89 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 90 | id = user['id'] 91 | session = user['session'] 92 | user_space:delete(id) 93 | 94 | got = {auth.check_auth(session), } 95 | expected = {response.error(error.USER_NOT_FOUND), } 96 | test:is_deeply(got, expected, 'test_check_auth_user_not_found') 97 | end 98 | 99 | function test_check_auth_user_not_active() 100 | local ok, user, id, session, got, expected 101 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 102 | id = user['id'] 103 | session = user['session'] 104 | 105 | user_space:update(id, {{'=', 4, false}}) 106 | 107 | got = {auth.check_auth(session), } 108 | expected = {response.error(error.USER_NOT_ACTIVE), } 109 | test:is_deeply(got, expected, 'test_check_auth_user_not_active') 110 | end 111 | 112 | function test_check_auth_empty_session() 113 | local ok, user, got, expected 114 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 115 | 116 | got = {auth.check_auth(''), } 117 | expected = {response.error(error.INVALID_PARAMS), } 118 | test:is_deeply(got, expected, 'test_check_auth_empty_session') 119 | end 120 | 121 | function test_check_auth_update_session_success() 122 | local ok, user, got, expected, first_session, second_session, user_id 123 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 124 | first_session = user['session'] 125 | user_id = user['id'] 126 | 127 | fiber.sleep(config.session_lifetime - config.session_update_timedelta) 128 | 129 | ok, user = auth.check_auth(first_session) 130 | test:is(ok, true, 'test_check_auth_update_session_success session updated') 131 | 132 | second_session = user['session'] 133 | test:isstring(second_session, 'test_check_auth_update_session_success session returned') 134 | test:isnt(first_session, second_session, 'test_check_auth_update_session_success new session') 135 | 136 | ok, user = auth.check_auth(second_session) 137 | test:is(user['id'], user_id, 'test_check_auth_update_session_success user returned') 138 | end 139 | 140 | function test_check_auth_expired_session() 141 | local ok, user, got, expected, session 142 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 143 | session = user['session'] 144 | 145 | fiber.sleep(config.session_lifetime) 146 | 147 | got = {auth.check_auth(session), } 148 | expected = {response.error(error.NOT_AUTHENTICATED), } 149 | test:is_deeply(got, expected, 'test_check_auth_expired_session') 150 | end 151 | 152 | function test_drop_session_success() 153 | local ok, user,session, expected, deleted, got 154 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 155 | session = user['session'] 156 | ok, deleted = auth.drop_session(session) 157 | test:is(ok, true, 'test_drop_session_success session droped') 158 | 159 | got = {auth.check_auth(session), } 160 | expected = {response.error(error.WRONG_SESSION_SIGN), } 161 | test:is_deeply(got, expected, 'test_drop_session_success wrong sign') 162 | end 163 | 164 | function test_drop_session_empty() 165 | local expected, got 166 | got = {auth.drop_session(''), } 167 | expected = {response.error(error.INVALID_PARAMS), } 168 | test:is_deeply(got, expected, 'test_drop_session_empty') 169 | end 170 | 171 | function test_drop_session_wrong_sign() 172 | local ok, user, got, expected 173 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 174 | got = {auth.drop_session('thissession.iswrongsigned'), } 175 | expected = {response.error(error.WRONG_SESSION_SIGN), } 176 | test:is_deeply(got, expected, 'test_drop_session_wrong_sign') 177 | end 178 | 179 | function test_drop_session_twice() 180 | local ok, user, got, expected, deleted, session 181 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 182 | session = user['session'] 183 | 184 | ok, deleted = auth.drop_session(session) 185 | got = {auth.drop_session(session), } 186 | 187 | expected = {response.error(error.WRONG_SESSION_SIGN), } 188 | test:is_deeply(got, expected, 'test_drop_session_twice') 189 | end 190 | 191 | 192 | exports.tests = { 193 | test_auth_success, 194 | test_check_auth_success, 195 | test_check_auth_update_session_success, 196 | test_drop_session_success, 197 | 198 | test_auth_wrong_password, 199 | test_auth_user_not_found, 200 | test_auth_user_not_active, 201 | test_check_auth_wrong_sign, 202 | test_check_auth_user_not_found, 203 | test_check_auth_user_not_active, 204 | test_check_auth_empty_session, 205 | test_check_auth_expired_session, 206 | test_drop_session_empty, 207 | test_drop_session_wrong_sign, 208 | test_drop_session_twice 209 | } 210 | 211 | return exports 212 | -------------------------------------------------------------------------------- /test/case/complex.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local db = require('authman.db') 4 | local validator = require('authman.validator') 5 | local v = require('test.values') 6 | 7 | -- model configuration 8 | local config = validator.config(require('test.config')) 9 | local db = require('authman.db').configurate(config) 10 | local auth = require('authman').api(config) 11 | 12 | local test = tap.test('auth_test') 13 | 14 | function exports.setup() end 15 | 16 | function exports.before() end 17 | 18 | function exports.after() 19 | db.truncate_spaces() 20 | end 21 | 22 | function exports.teardown() end 23 | 24 | function test_register_social_and_common() 25 | local ok, code, common_user, common_session, social_user, social_session, expected 26 | 27 | local profile = { 28 | first_name = v.USER_FIRST_NAME, 29 | last_name = v.USER_LAST_NAME 30 | } 31 | 32 | ok, common_user = auth.registration(v.USER_EMAIL) 33 | ok, common_user = auth.complete_registration(v.USER_EMAIL, common_user.code, v.USER_PASSWORD) 34 | ok, common_user = auth.set_profile(common_user['id'], profile) 35 | 36 | ok, social_user = auth.social_auth('vk', v.VALID_CODE) 37 | 38 | test:isnt(social_user['id'], common_user['id'], 'test_register_social_and_common users created') 39 | common_user['id'] = nil 40 | social_user['id'] = nil 41 | 42 | 43 | expected = { 44 | provider = 'vk', 45 | social_id = v.SOCIAL_ID 46 | } 47 | test:is_deeply(social_user['social'], expected, 'test_register_social_and_common social data') 48 | social_user['social'] = nil 49 | social_user['session'] = nil 50 | test:is_deeply(common_user, social_user, 'test_register_social_and_common profile equal') 51 | end 52 | 53 | 54 | function test_auth_social_and_common() 55 | local ok, code, common_user, common_session, social_user, social_session, expected 56 | 57 | ok, common_user = auth.registration(v.USER_EMAIL) 58 | ok, common_user = auth.complete_registration(v.USER_EMAIL, common_user.code, v.USER_PASSWORD) 59 | ok, common_user = auth.auth(v.USER_EMAIL, v.USER_PASSWORD) 60 | common_session = common_user['session'] 61 | 62 | ok, social_user = auth.social_auth('vk', v.VALID_CODE) 63 | social_session = social_user['session'] 64 | 65 | test:isnt(social_user['id'], common_user['id'], 'test_auth_social_and_common users created') 66 | test:isnt(social_session, common_session, 'test_auth_social_and_common different sessions') 67 | 68 | ok, common_user = auth.check_auth(common_session) 69 | test:is(ok, true, 'test_auth_social_and_common common auth') 70 | ok, social_user = auth.check_auth(social_session) 71 | test:is(ok, true, 'test_auth_social_and_common social auth') 72 | end 73 | 74 | 75 | function test_complex_password() 76 | local ok, code, user, session, password, expected 77 | password = '123_this pa$$word % CoMpLeX as HeLL...яйца' 78 | ok, user = auth.registration(v.USER_EMAIL) 79 | ok, user = auth.complete_registration(v.USER_EMAIL, user.code, password) 80 | ok, user = auth.auth(v.USER_EMAIL, password) 81 | 82 | test:is(ok, true, 'test_complex_password') 83 | end 84 | 85 | 86 | exports.tests = { 87 | test_register_social_and_common, 88 | test_auth_social_and_common, 89 | test_complex_password, 90 | } 91 | 92 | return exports -------------------------------------------------------------------------------- /test/case/oauth/redirect.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local response = require('authman.response') 4 | local error = require('authman.error') 5 | local validator = require('authman.validator') 6 | local v = require('test.values') 7 | 8 | -- model configuration 9 | local config = validator.config(require('test.config')) 10 | local db = require('authman.db').configurate(config) 11 | local auth = require('authman').api(config) 12 | local utils = require('authman.utils.utils') 13 | 14 | local test = tap.test('oauth_redirect_test') 15 | 16 | 17 | function exports.setup() end 18 | 19 | function exports.before() 20 | db.truncate_spaces() 21 | end 22 | 23 | function exports.after() end 24 | 25 | function exports.teardown() end 26 | 27 | function test_save_redirect_success() 28 | 29 | local user_id = "user_id" 30 | local redirect_url = "http://test.ru/test1" 31 | local got, expected 32 | 33 | 34 | got = {auth.oauth.save_redirect(v.OAUTH_CONSUMER_KEY, user_id, redirect_url)} 35 | expected = {true, { 36 | consumer_key = v.OAUTH_CONSUMER_KEY, 37 | user_id = user_id, 38 | url = redirect_url 39 | }} 40 | 41 | test:is_deeply(got, expected, 'test_save_redirect_success; url1') 42 | 43 | redirect_url = "http://test.ru/test2" 44 | 45 | got = {auth.oauth.save_redirect(v.OAUTH_CONSUMER_KEY, user_id, redirect_url)} 46 | expected = {true, { 47 | consumer_key = v.OAUTH_CONSUMER_KEY, 48 | user_id = user_id, 49 | url = redirect_url 50 | }} 51 | 52 | test:is_deeply(got, expected, 'test_save_redirect_success; url2') 53 | end 54 | 55 | function test_save_redirect_invalid_params() 56 | 57 | local user_id = "user_id" 58 | local got, expected 59 | local redirect_url = "http://test.ru/test1" 60 | 61 | got = {auth.oauth.save_redirect(nil, user_id, redirect_url)} 62 | expected = {response.error(error.INVALID_PARAMS)} 63 | test:is_deeply(got, expected, 'test_save_redirect_invalid_params; nil consumer key') 64 | 65 | got = {auth.oauth.save_redirect(v.OAUTH_CONSUMER_KEY, nil, redirect_url)} 66 | expected = {response.error(error.INVALID_PARAMS)} 67 | test:is_deeply(got, expected, 'test_save_redirect_invalid_params; nil user_id') 68 | 69 | got = {auth.oauth.save_redirect(v.OAUTH_CONSUMER_KEY, user_id, nil)} 70 | expected = {response.error(error.INVALID_PARAMS)} 71 | test:is_deeply(got, expected, 'test_save_redirect_invalid_params; nil redirect url') 72 | end 73 | 74 | function test_get_user_redirects_success() 75 | 76 | local got, expected 77 | 78 | local _, user = auth.registration(v.USER_EMAIL) 79 | _, user = auth.complete_registration(v.USER_EMAIL, user.code, v.USER_PASSWORD) 80 | 81 | local _, app1 = auth.oauth.add_app(user.id, "Test app 1", 'server', v.OAUTH_CONSUMER_REDIRECT_URLS) 82 | local _, app2 = auth.oauth.add_app(user.id, "Test app 2", 'native', v.OAUTH_CONSUMER_REDIRECT_URLS) 83 | 84 | local _, consumer1 = auth.oauth.get_consumer(app1.consumer_key) 85 | local _, consumer2 = auth.oauth.get_consumer(app2.consumer_key) 86 | 87 | auth.oauth.save_redirect(app1.consumer_key, "user_id1", "http://test.ru/1") 88 | auth.oauth.save_redirect(app2.consumer_key, "user_id2", "http://test.ru/2") 89 | 90 | got = {auth.oauth.get_user_redirects("user_id1")} 91 | 92 | expected = {true, { 93 | {consumer_key = app1.consumer_key, user_id = "user_id1", url = "http://test.ru/1", consumer = consumer1}, 94 | }} 95 | 96 | test:is_deeply(got, expected, 'test_get_user_redirects_success; user_id1') 97 | 98 | got = {auth.oauth.get_user_redirects("user_id2")} 99 | expected = {true, { 100 | {consumer_key = app2.consumer_key, user_id = "user_id2", url = "http://test.ru/2", consumer = consumer2}, 101 | }} 102 | test:is_deeply(got, expected, 'test_get_user_redirects_success; user_id2') 103 | end 104 | 105 | function test_get_user_redirects_consumer_not_found() 106 | 107 | local got, expected 108 | 109 | auth.oauth.save_redirect("consumer_key1", "user_id1", "http://test.ru/1") 110 | auth.oauth.save_redirect("consumer_key2", "user_id1", "http://test.ru/2") 111 | 112 | got = {auth.oauth.get_user_redirects("user_id1")} 113 | expected = {true, {}} 114 | 115 | test:is_deeply(got, expected, 'test_get_user_redirects_consumer_not_found') 116 | end 117 | 118 | function test_get_user_redirects_invalid_params() 119 | 120 | local redirect_url = "http://test.ru/test1" 121 | auth.oauth.save_redirect(v.OAUTH_CONSUMER_KEY, "user_id1", redirect_url) 122 | 123 | local got = {auth.oauth.get_user_redirects()} 124 | local expected = {response.error(error.INVALID_PARAMS)} 125 | 126 | test:is_deeply(got, expected, 'test_get_user_redirects_invalid_params') 127 | end 128 | 129 | function test_delete_user_redirects_success() 130 | local got, expected 131 | 132 | auth.oauth.save_redirect("consumer_key1", "user_id1", "http://test.ru/1") 133 | auth.oauth.save_redirect("consumer_key1", "user_id2", "http://test.ru/2") 134 | auth.oauth.save_redirect("consumer_key2", "user_id1", "http://test.ru/3") 135 | auth.oauth.save_redirect("consumer_key2", "user_id2", "http://test.ru/4") 136 | 137 | got = {auth.oauth.delete_user_redirects("user_id1", "consumer_key1")} 138 | expected = {true, { 139 | {consumer_key = "consumer_key1", user_id = "user_id1", url = "http://test.ru/1"}, 140 | }} 141 | test:is_deeply(got, expected, 'test_delete_user_redirects_success; user_id1') 142 | 143 | got = {auth.oauth.delete_user_redirects("user_id1", "consumer_key2")} 144 | expected = {true, { 145 | {consumer_key = "consumer_key2", user_id = "user_id1", url = "http://test.ru/3"}, 146 | }} 147 | test:is_deeply(got, expected, 'test_delete_user_redirects_success; user_id1') 148 | 149 | got = {auth.oauth.delete_user_redirects("user_id2", "consumer_key1")} 150 | expected = {true, { 151 | {consumer_key = "consumer_key1", user_id = "user_id2", url = "http://test.ru/2"}, 152 | }} 153 | test:is_deeply(got, expected, 'test_delete_user_redirects_success; user_id2') 154 | 155 | got = {auth.oauth.delete_user_redirects("user_id2", "consumer_key2")} 156 | expected = {true, { 157 | {consumer_key = "consumer_key2", user_id = "user_id2", url = "http://test.ru/4"}, 158 | }} 159 | test:is_deeply(got, expected, 'test_delete_user_redirects_success; user_id2') 160 | 161 | got = {auth.oauth.get_user_redirects("user_id1")} 162 | expected = {true, {}} 163 | test:is_deeply(got, expected, 'test_delete_user_redirects_success; user_id1; deleted') 164 | 165 | got = {auth.oauth.get_user_redirects("user_id2")} 166 | expected = {true, {}} 167 | test:is_deeply(got, expected, 'test_delete_user_redirects_success; user_id2; deleted') 168 | 169 | end 170 | 171 | function test_delete_user_redirects_invalid_params() 172 | local got, expected 173 | 174 | auth.oauth.save_redirect("consumer_key1", "user_id1", "http://test.ru/1") 175 | 176 | got = {auth.oauth.delete_user_redirects()} 177 | local expected = {response.error(error.INVALID_PARAMS)} 178 | end 179 | 180 | function test_delete_app() 181 | 182 | local ok, got, expected 183 | 184 | local _, user = auth.registration(v.USER_EMAIL) 185 | _, user = auth.complete_registration(v.USER_EMAIL, user.code, v.USER_PASSWORD) 186 | 187 | local _, app1 = auth.oauth.add_app(user.id, "Test app 1", 'server', v.OAUTH_CONSUMER_REDIRECT_URLS) 188 | auth.oauth.save_redirect(app1.consumer_key, 'user_id2', v.OAUTH_CONSUMER_REDIRECT_URLS) 189 | 190 | local _, app2 = auth.oauth.add_app(user.id, "Test app 2", 'browser', v.OAUTH_CONSUMER_REDIRECT_URLS) 191 | auth.oauth.save_redirect(app2.consumer_key, 'user_id2', v.OAUTH_CONSUMER_REDIRECT_URLS) 192 | 193 | auth.oauth.delete_app(app1.id) 194 | 195 | got = {auth.oauth.get_user_redirects("user_id2", app1.consumer_key)} 196 | local _, expected_consumer = auth.oauth.get_consumer(app2.consumer_key) 197 | expected = {true, { 198 | {consumer_key = app2.consumer_key, user_id = "user_id2", url = v.OAUTH_CONSUMER_REDIRECT_URLS, consumer = expected_consumer}, 199 | }} 200 | test:is_deeply(got, expected, 'test_delete_app; app1 redirect urls deleted') 201 | end 202 | 203 | function test_get_consumer_redirects_success() 204 | 205 | local got, expected 206 | 207 | auth.oauth.save_redirect("consumer_key1", "user_id1", "http://test.ru/1") 208 | auth.oauth.save_redirect("consumer_key1", "user_id2", "http://test.ru/2") 209 | auth.oauth.save_redirect("consumer_key2", "user_id1", "http://test.ru/3") 210 | auth.oauth.save_redirect("consumer_key2", "user_id2", "http://test.ru/4") 211 | 212 | got = {auth.oauth.get_consumer_redirects("consumer_key1", "user_id1")} 213 | expected = {true, { 214 | {consumer_key = "consumer_key1", user_id = "user_id1", url = "http://test.ru/1"}, 215 | }} 216 | 217 | test:is_deeply(got, expected, 'test_get_consumer_redirects_success; consumer_key1; user_id1') 218 | 219 | got = {auth.oauth.get_consumer_redirects("consumer_key1", "user_id2")} 220 | expected = {true, { 221 | {consumer_key = "consumer_key1", user_id = "user_id2", url = "http://test.ru/2"}, 222 | }} 223 | 224 | test:is_deeply(got, expected, 'test_get_consumer_redirects_success; consumer_key1; user_id2') 225 | 226 | got = {auth.oauth.get_consumer_redirects("consumer_key2")} 227 | expected = {true, { 228 | {consumer_key = "consumer_key2", user_id = "user_id1", url = "http://test.ru/3"}, 229 | {consumer_key = "consumer_key2", user_id = "user_id2", url = "http://test.ru/4"}, 230 | }} 231 | test:is_deeply(got, expected, 'test_get_consumer_redirects_success; consumer_key2') 232 | end 233 | 234 | function test_get_consumer_redirects_invalid_params() 235 | 236 | auth.oauth.save_redirect(v.OAUTH_CONSUMER_KEY, "user_id1", v.OAUTH_CONSUMER_REDIRECT_URL) 237 | 238 | local got = {auth.oauth.get_consumer_redirects()} 239 | local expected = {response.error(error.INVALID_PARAMS)} 240 | 241 | test:is_deeply(got, expected, 'test_get_consumer_redirects_invalid_params') 242 | end 243 | 244 | 245 | 246 | exports.tests = { 247 | test_save_redirect_success, 248 | test_save_redirect_invalid_params, 249 | test_get_user_redirects_success, 250 | test_get_user_redirects_consumer_not_found, 251 | test_get_user_redirects_invalid_params, 252 | test_delete_user_redirects_success, 253 | test_delete_user_redirects_invalid_params, 254 | test_delete_app, 255 | test_get_consumer_redirects_success, 256 | test_get_consumer_redirects_invalid_params, 257 | } 258 | 259 | 260 | return exports 261 | -------------------------------------------------------------------------------- /test/case/oauth/scope.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local response = require('authman.response') 4 | local error = require('authman.error') 5 | local validator = require('authman.validator') 6 | local v = require('test.values') 7 | 8 | -- model configuration 9 | local config = validator.config(require('test.config')) 10 | local db = require('authman.db').configurate(config) 11 | local auth = require('authman').api(config) 12 | local utils = require('authman.utils.utils') 13 | 14 | local test = tap.test('oauth_scope_test') 15 | 16 | 17 | function exports.setup() end 18 | 19 | function exports.before() 20 | db.truncate_spaces() 21 | end 22 | 23 | function exports.after() end 24 | 25 | function exports.teardown() end 26 | 27 | function test_add_consumer_scopes_success() 28 | 29 | local user_id = "user_id" 30 | local scopes, got, expected 31 | 32 | scopes = {"scope1", "scope2", "scope3"} 33 | 34 | got = {auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, user_id, scopes)} 35 | expected = {true, { 36 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope1"}, 37 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope2"}, 38 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope3"}, 39 | }} 40 | 41 | test:is_deeply(got, expected, 'test_add_consumer_scopes_success; added 3 scopes') 42 | 43 | scopes = {"scope3", "scope4", "scope5"} 44 | 45 | got = {auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, user_id, scopes)} 46 | expected = {true, { 47 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope1"}, 48 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope2"}, 49 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope3"}, 50 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope4"}, 51 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope5"}, 52 | }} 53 | 54 | test:is_deeply(got, expected, 'test_add_consumer_scopes_success; added 2 of 3 scopes') 55 | 56 | scopes = {"scope3", "scope4"} 57 | 58 | got = {auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, user_id, scopes)} 59 | expected = {true, { 60 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope1"}, 61 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope2"}, 62 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope3"}, 63 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope4"}, 64 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope5"}, 65 | }} 66 | 67 | test:is_deeply(got, expected, 'test_add_consumer_scopes_success; added 0 scopes') 68 | 69 | got = {auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, user_id, {})} 70 | expected = {true, { 71 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope1"}, 72 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope2"}, 73 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope3"}, 74 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope4"}, 75 | {user_id = user_id, consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope5"}, 76 | }} 77 | 78 | test:is_deeply(got, expected, 'test_add_consumer_scopes_success; added 0 scopes') 79 | end 80 | 81 | function test_add_consumer_scopes_invalid_params() 82 | 83 | local user_id = "user_id" 84 | local got, expected 85 | 86 | local scopes = {"scope1", "scope2", "scope3"} 87 | 88 | got = {auth.oauth.add_consumer_scopes(nil, user_id, scopes)} 89 | expected = {response.error(error.INVALID_PARAMS)} 90 | 91 | test:is_deeply(got, expected, 'test_add_consumer_scopes_invalid_params; nil consumer key') 92 | 93 | got = {auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, nil, scopes)} 94 | expected = {response.error(error.INVALID_PARAMS)} 95 | 96 | test:is_deeply(got, expected, 'test_add_consumer_scopes_invalid_params; nil user_id') 97 | end 98 | 99 | function test_get_user_authorizations_success() 100 | 101 | local scopes, got, expected 102 | 103 | local _, user = auth.registration(v.USER_EMAIL) 104 | _, user = auth.complete_registration(v.USER_EMAIL, user.code, v.USER_PASSWORD) 105 | 106 | local _, app = auth.oauth.add_app(user.id, "Test app 1", 'server', v.OAUTH_CONSUMER_REDIRECT_URLS) 107 | local _, expected_consumer = auth.oauth.get_consumer(app.consumer_key) 108 | 109 | scopes = {"scope1", "scope2", "scope3"} 110 | 111 | auth.oauth.add_consumer_scopes(app.consumer_key, "user_id1", scopes) 112 | 113 | scopes = {"scope4", "scope5", "scope6"} 114 | 115 | auth.oauth.add_consumer_scopes(app.consumer_key, "user_id2", scopes) 116 | 117 | got = {auth.oauth.get_user_authorizations("user_id1")} 118 | expected = {true, { 119 | {user_id = "user_id1", consumer_key = app.consumer_key, name = "scope1", consumer = expected_consumer}, 120 | {user_id = "user_id1", consumer_key = app.consumer_key, name = "scope2", consumer = expected_consumer}, 121 | {user_id = "user_id1", consumer_key = app.consumer_key, name = "scope3", consumer = expected_consumer}, 122 | }} 123 | test:is_deeply(got, expected, 'test_get_user_authorizations_success; user_id1') 124 | 125 | got = {auth.oauth.get_user_authorizations("user_id2")} 126 | expected = {true, { 127 | {user_id = "user_id2", consumer_key = app.consumer_key, name = "scope4", consumer = expected_consumer}, 128 | {user_id = "user_id2", consumer_key = app.consumer_key, name = "scope5", consumer = expected_consumer}, 129 | {user_id = "user_id2", consumer_key = app.consumer_key, name = "scope6", consumer = expected_consumer}, 130 | }} 131 | test:is_deeply(got, expected, 'test_get_user_authorizations_success; user_id2') 132 | end 133 | 134 | 135 | function test_get_user_authorizations_invalid_params() 136 | 137 | local scopes = {"scope1", "scope2", "scope3"} 138 | 139 | auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, "user_id1", scopes) 140 | 141 | local got = {auth.oauth.get_user_authorizations()} 142 | local expected = {response.error(error.INVALID_PARAMS)} 143 | 144 | test:is_deeply(got, expected, 'test_get_user_authorizations_invalid_params') 145 | end 146 | 147 | function test_delete_user_authorizations_success() 148 | 149 | local scopes, got, expected 150 | 151 | scopes = {"scope1", "scope2", "scope3"} 152 | 153 | auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, "user_id1", scopes) 154 | 155 | scopes = {"scope4", "scope5", "scope6"} 156 | 157 | auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, "user_id2", scopes) 158 | 159 | got = {auth.oauth.delete_user_authorizations("user_id1", v.OAUTH_CONSUMER_KEY)} 160 | expected = {true, { 161 | {user_id = "user_id1", consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope1"}, 162 | {user_id = "user_id1", consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope2"}, 163 | {user_id = "user_id1", consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope3"}, 164 | }} 165 | test:is_deeply(got, expected, 'test_delete_user_authorizations_success; user_id1') 166 | 167 | got = {auth.oauth.delete_user_authorizations("user_id2", v.OAUTH_CONSUMER_KEY)} 168 | expected = {true, { 169 | {user_id = "user_id2", consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope4"}, 170 | {user_id = "user_id2", consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope5"}, 171 | {user_id = "user_id2", consumer_key = v.OAUTH_CONSUMER_KEY, name = "scope6"}, 172 | }} 173 | test:is_deeply(got, expected, 'test_delete_user_authorizations_success; user_id2') 174 | end 175 | 176 | function test_delete_app() 177 | 178 | local ok, got, expected 179 | 180 | local _, user = auth.registration(v.USER_EMAIL) 181 | _, user = auth.complete_registration(v.USER_EMAIL, user.code, v.USER_PASSWORD) 182 | 183 | local _, app1 = auth.oauth.add_app(user.id, "Test app 1", 'server', v.OAUTH_CONSUMER_REDIRECT_URLS) 184 | auth.oauth.add_consumer_scopes(app1.consumer_key, v.OAUTH_RESOURCE_OWNER, {v.OAUTH_SCOPE}) 185 | 186 | local _, app2 = auth.oauth.add_app(user.id, "Test app 2", 'browser', v.OAUTH_CONSUMER_REDIRECT_URLS) 187 | auth.oauth.add_consumer_scopes(app2.consumer_key, v.OAUTH_RESOURCE_OWNER, {v.OAUTH_SCOPE}) 188 | 189 | auth.oauth.delete_app(app1.id) 190 | 191 | got = {auth.oauth.get_consumer_authorizations(app1.consumer_key, v.OAUTH_RESOURCE_OWNER)} 192 | expected = {true, {}} 193 | test:is_deeply(got, expected, 'test_delete_app; app1 authorization is deleted') 194 | 195 | got = {auth.oauth.get_consumer_authorizations(app2.consumer_key, v.OAUTH_RESOURCE_OWNER)} 196 | expected = {true, { 197 | {user_id = v.OAUTH_RESOURCE_OWNER, consumer_key = app2.consumer_key, name = v.OAUTH_SCOPE}, 198 | }} 199 | test:is_deeply(got, expected, 'test_delete_app; app2 authorization is not deleted') 200 | end 201 | 202 | function test_get_consumer_authorizations_success() 203 | 204 | local scopes, got, expected 205 | 206 | scopes = {"scope1", "scope2", "scope3"} 207 | 208 | auth.oauth.add_consumer_scopes("consumer_key1", "user_id", scopes) 209 | 210 | scopes = {"scope4", "scope5", "scope6"} 211 | 212 | auth.oauth.add_consumer_scopes("consumer_key2", "user_id", scopes) 213 | 214 | got = {auth.oauth.get_consumer_authorizations("consumer_key1")} 215 | expected = {true, { 216 | {user_id = "user_id", consumer_key = "consumer_key1", name = "scope1"}, 217 | {user_id = "user_id", consumer_key = "consumer_key1", name = "scope2"}, 218 | {user_id = "user_id", consumer_key = "consumer_key1", name = "scope3"}, 219 | }} 220 | test:is_deeply(got, expected, 'test_get_consumer_authorizations_success; consumer_key1') 221 | 222 | got = {auth.oauth.get_consumer_authorizations("consumer_key2")} 223 | expected = {true, { 224 | {user_id = "user_id", consumer_key = "consumer_key2", name = "scope4"}, 225 | {user_id = "user_id", consumer_key = "consumer_key2", name = "scope5"}, 226 | {user_id = "user_id", consumer_key = "consumer_key2", name = "scope6"}, 227 | }} 228 | test:is_deeply(got, expected, 'test_get_consumer_authorizations_success; consumer_key2') 229 | end 230 | 231 | function test_get_consumer_authorizations_invalid_params() 232 | 233 | local scopes = {"scope1", "scope2", "scope3"} 234 | 235 | auth.oauth.add_consumer_scopes(v.OAUTH_CONSUMER_KEY, "user_id1", scopes) 236 | 237 | local got = {auth.oauth.get_consumer_authorizations()} 238 | local expected = {response.error(error.INVALID_PARAMS)} 239 | 240 | test:is_deeply(got, expected, 'test_get_consumer_authorizations_invalid_params') 241 | end 242 | 243 | 244 | 245 | exports.tests = { 246 | test_add_consumer_scopes_success, 247 | test_add_consumer_scopes_invalid_params, 248 | test_get_user_authorizations_success, 249 | test_get_user_authorizations_invalid_params, 250 | test_delete_user_authorizations_success, 251 | test_delete_app, 252 | test_get_consumer_authorizations_success, 253 | test_get_consumer_authorizations_invalid_params, 254 | } 255 | 256 | 257 | return exports 258 | -------------------------------------------------------------------------------- /test/case/profile.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local response = require('authman.response') 4 | local error = require('authman.error') 5 | local validator = require('authman.validator') 6 | local v = require('test.values') 7 | 8 | -- model configuration 9 | local config = validator.config(require('test.config')) 10 | local db = require('authman.db').configurate(config) 11 | local auth = require('authman').api(config) 12 | local user_space = require('authman.model.user').model(config).get_space() 13 | 14 | local test = tap.test('auth_test') 15 | 16 | function exports.setup() end 17 | 18 | function exports.before() end 19 | 20 | function exports.after() 21 | db.truncate_spaces() 22 | end 23 | 24 | function exports.teardown() end 25 | 26 | 27 | function test_set_profile_success() 28 | local user_profile, ok, user, code, expected 29 | ok, user = auth.registration('test@test.ru') 30 | ok, user = auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 31 | user_profile = {last_name='test_last', first_name='test_first' } 32 | 33 | ok, user = auth.set_profile(user['id'], user_profile) 34 | 35 | user['id'] = nil 36 | expected = {email = 'test@test.ru', is_active = true, profile=user_profile} 37 | test:is(ok, true, 'test_set_profile_success user returned') 38 | test:is_deeply(user, expected, 'test_set_profile_success profile set') 39 | end 40 | 41 | function test_set_profile_invalid_id() 42 | local got, expected, user_profile 43 | user_profile = {last_name='test_last', first_name='test_first' } 44 | 45 | got = {auth.set_profile('', user_profile), } 46 | expected = {response.error(error.INVALID_PARAMS), } 47 | test:is_deeply(got, expected, 'test_set_profile_invalid_id') 48 | end 49 | 50 | function test_set_profile_user_not_found() 51 | local got, expected, user_profile 52 | user_profile = {last_name='test_last', first_name='test_first' } 53 | 54 | got = {auth.set_profile('not exists', user_profile), } 55 | expected = {response.error(error.USER_NOT_FOUND), } 56 | test:is_deeply(got, expected, 'test_set_user_not_found') 57 | end 58 | 59 | function test_set_profile_user_not_active() 60 | local got, expected, user_profile, ok, code, user, id 61 | ok, user = auth.registration('test@test.ru') 62 | ok, user = auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 63 | id = user['id'] 64 | 65 | user_space:update(id, {{'=', 4, false}}) 66 | user_profile = {last_name='test_last', first_name='test_first' } 67 | 68 | got = {auth.set_profile(id, user_profile), } 69 | expected = {response.error(error.USER_NOT_ACTIVE), } 70 | test:is_deeply(got, expected, 'test_set_profile_user_not_active') 71 | end 72 | 73 | function test_get_profile_success() 74 | local user_profile, ok, user, code, expected 75 | ok, user = auth.registration('test@test.ru') 76 | ok, user = auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 77 | user_profile = {last_name='test_last', first_name='test_first' } 78 | 79 | ok, user = auth.set_profile(user['id'], user_profile) 80 | ok, user = auth.get_profile(user['id']) 81 | 82 | user['id'] = nil 83 | expected = {email = 'test@test.ru', is_active = true, profile=user_profile} 84 | test:is(ok, true, 'test_get_profile_success user returned') 85 | test:is_deeply(user, expected, 'test_get_profile_success profile') 86 | end 87 | 88 | function test_get_profile_invalid_id() 89 | local got, expected, user_profile 90 | 91 | got = {auth.get_profile(''), } 92 | expected = {response.error(error.INVALID_PARAMS), } 93 | test:is_deeply(got, expected, 'test_set_profile_invalid_id') 94 | end 95 | 96 | function test_get_profile_user_not_found() 97 | local got, expected, user_profile 98 | 99 | got = {auth.get_profile('not exists'), } 100 | expected = {response.error(error.USER_NOT_FOUND), } 101 | test:is_deeply(got, expected, 'test_set_user_not_found') 102 | end 103 | 104 | function test_delete_user_success() 105 | local user_profile, ok, user, code, expected, got, id 106 | ok, user = auth.registration('test@test.ru') 107 | ok, user = auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 108 | user_profile = {last_name='test_last', first_name='test_first' } 109 | 110 | ok, user = auth.delete_user(user['id']) 111 | id = user['id'] 112 | user['id'] = nil 113 | expected = {email = 'test@test.ru', is_active = true} 114 | test:is(ok, true, 'test_delete_user_success user deleted') 115 | test:is_deeply(user, expected, 'test_delete_user_success profile returned') 116 | 117 | got = {auth.get_profile(id), } 118 | expected = {response.error(error.USER_NOT_FOUND), } 119 | test:is_deeply(got, expected, 'test_delete_user_success user not found') 120 | end 121 | 122 | function test_delete_user_invalid_id() 123 | local got, expected, user_profile 124 | 125 | got = {auth.delete_user(''), } 126 | expected = {response.error(error.INVALID_PARAMS), } 127 | test:is_deeply(got, expected, 'test_delete_user_invalid_id') 128 | end 129 | 130 | function test_delete_user_user_not_found() 131 | local got, expected, user_profile 132 | 133 | got = {auth.delete_user('not exists'), } 134 | expected = {response.error(error.USER_NOT_FOUND), } 135 | test:is_deeply(got, expected, 'test_delete_user_user_not_found') 136 | end 137 | 138 | exports.tests = { 139 | test_set_profile_success, 140 | test_get_profile_success, 141 | test_delete_user_success, 142 | 143 | test_set_profile_invalid_id, 144 | test_set_profile_user_not_found, 145 | test_set_profile_user_not_active, 146 | test_get_profile_invalid_id, 147 | test_get_profile_user_not_found, 148 | test_delete_user_invalid_id, 149 | test_delete_user_user_not_found, 150 | } 151 | 152 | return exports 153 | -------------------------------------------------------------------------------- /test/case/registration.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local response = require('authman.response') 4 | local error = require('authman.error') 5 | local validator = require('authman.validator') 6 | local v = require('test.values') 7 | 8 | -- model configuration 9 | local config = validator.config(require('test.config')) 10 | local db = require('authman.db').configurate(config) 11 | local auth = require('authman').api(config) 12 | 13 | local test = tap.test('registration_test') 14 | 15 | function exports.setup() end 16 | 17 | function exports.before() 18 | db.truncate_spaces() 19 | end 20 | 21 | function exports.after() end 22 | 23 | function exports.teardown() end 24 | 25 | function test_registration_succes() 26 | local ok, user 27 | ok, user = auth.registration('test@test.ru') 28 | test:is(ok, true, 'test_registration_succes user created') 29 | test:isstring(user.code, 'test_registration_succes code returned') 30 | test:is(user.email, 'test@test.ru', 'test_registration_succes email returned') 31 | end 32 | 33 | function test_registartion_user_not_active_success() 34 | local ok, user 35 | ok, user = auth.registration('test_exists@test.ru') 36 | ok, user = auth.registration('test_exists@test.ru') 37 | test:is(ok, true, 'test_registartion_user_not_active_success user created') 38 | test:isstring(user.code, 'test_registartion_user_not_active_success code returned') 39 | test:is(user.email, 'test_exists@test.ru', 'test_registartion_user_not_active_success email returned') 40 | end 41 | 42 | function test_complete_registration_succes() 43 | local ok, user 44 | ok, user = auth.registration('test@test.ru') 45 | ok, user = auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 46 | 47 | user['id'] = nil -- remove random id 48 | test:is(ok, true, 'test_complete_registration_succes activated success') 49 | test:is_deeply(user, {email = 'test@test.ru', is_active = true}, 'test_complete_registration_succes user returned') 50 | end 51 | 52 | function test_registration_invalid_email() 53 | local got, expected 54 | expected = {response.error(error.INVALID_PARAMS), } 55 | got = {auth.registration('not_email'), } 56 | test:is_deeply(got, expected, 'test_registration_invalid_email not email') 57 | got = {auth.registration(''), } 58 | test:is_deeply(got, expected, 'test_registration_invalid_email empty email') 59 | end 60 | 61 | function test_registration_user_already_active() 62 | local ok, got, expected, user 63 | ok, user = auth.registration('test@test.ru') 64 | ok, user = auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 65 | got = {auth.registration('test@test.ru'), } 66 | expected = {response.error(error.USER_ALREADY_EXISTS), } 67 | test:is_deeply(got, expected, 'test_registration_user_already_active') 68 | end 69 | 70 | function test_complete_registration_wrong_code() 71 | local ok, user, got, expected 72 | ok, user = auth.registration('test@test.ru') 73 | got = {auth.complete_registration('test@test.ru', 'bad_code', v.USER_PASSWORD), } 74 | expected = {response.error(error.WRONG_ACTIVATION_CODE), } 75 | test:is_deeply(got, expected, 'test_complete_registration_wrong_code') 76 | end 77 | 78 | function test_complete_registration_weak_password() 79 | local ok, user, got, expected 80 | ok, user = auth.registration('test@test.ru') 81 | 82 | got = {auth.complete_registration('test@test.ru', user.code, 'weak'), } 83 | expected = {response.error(error.WEAK_PASSWORD), } 84 | test:is_deeply(got, expected, 'test_complete_registration_weak_password 1') 85 | 86 | got = {auth.complete_registration('test@test.ru', user.code, '123123123'), } 87 | expected = {response.error(error.WEAK_PASSWORD), } 88 | test:is_deeply(got, expected, 'test_complete_registration_weak_password 2') 89 | 90 | got = {auth.complete_registration('test@test.ru', user.code, 'слабый'), } 91 | expected = {response.error(error.WEAK_PASSWORD), } 92 | test:is_deeply(got, expected, 'test_complete_registration_weak_password 3') 93 | end 94 | 95 | function test_complete_registration_user_already_active() 96 | local ok, user, got, expected, user, code 97 | ok, user = auth.registration('test@test.ru') 98 | code = user.code 99 | ok, user = auth.complete_registration('test@test.ru', code, v.USER_PASSWORD) 100 | got = {auth.complete_registration('test@test.ru', code, v.USER_PASSWORD), } 101 | expected = {response.error(error.USER_ALREADY_ACTIVE), } 102 | test:is_deeply(got, expected, 'test_complete_registration_user_already_active') 103 | end 104 | 105 | function test_complete_registration_user_not_found() 106 | local got, expected 107 | got = {auth.complete_registration('not_found@test.ru', 'some_code_here', v.USER_PASSWORD), } 108 | expected = {response.error(error.USER_NOT_FOUND), } 109 | test:is_deeply(got, expected, 'test_complete_registration_user_not_found') 110 | end 111 | 112 | function test_complete_registration_empty_code() 113 | local ok, user, got, expected 114 | ok, user = auth.registration('test@test.ru') 115 | got = {auth.complete_registration('test@test.ru', '', v.USER_PASSWORD), } 116 | expected = {response.error(error.INVALID_PARAMS), } 117 | test:is_deeply(got, expected, 'test_complete_registration_empty_code') 118 | end 119 | 120 | exports.tests = { 121 | test_registration_succes, 122 | test_registartion_user_not_active_success, 123 | test_complete_registration_succes, 124 | 125 | test_registration_invalid_email, 126 | test_registration_user_already_active, 127 | test_complete_registration_wrong_code, 128 | test_complete_registration_weak_password, 129 | test_complete_registration_user_already_active, 130 | test_complete_registration_user_not_found, 131 | test_complete_registration_empty_code, 132 | } 133 | 134 | return exports -------------------------------------------------------------------------------- /test/case/restore_password.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local response = require('authman.response') 4 | local error = require('authman.error') 5 | local validator = require('authman.validator') 6 | local v = require('test.values') 7 | 8 | -- model configuration 9 | local config = validator.config(require('test.config')) 10 | local db = require('authman.db').configurate(config) 11 | local auth = require('authman').api(config) 12 | local user_space = require('authman.model.user').model(config).get_space() 13 | local get_id_by_email = require('authman.model.user').model(config).get_id_by_email 14 | 15 | local test = tap.test('restore_pwd_test') 16 | 17 | function exports.setup() end 18 | 19 | function exports.before() 20 | local ok, user 21 | ok, user = auth.registration('test@test.ru') 22 | auth.complete_registration('test@test.ru', user.code, v.USER_PASSWORD) 23 | ok, user = auth.registration('not_active@test.ru') 24 | end 25 | 26 | function exports.after() 27 | db.truncate_spaces() 28 | end 29 | 30 | function exports.teardown() end 31 | 32 | function test_restore_password_success() 33 | local ok, token 34 | ok, token = auth.restore_password('test@test.ru') 35 | test:is(ok, true, 'test_restore_password_success password restored') 36 | test:isstring(token, 'test_restore_password_success token returned') 37 | end 38 | 39 | function test_complete_restore_password_success() 40 | local ok, token, user, expected, session, nok 41 | ok, user = auth.auth('test@test.ru', v.USER_PASSWORD) 42 | session = user['session'] 43 | ok, token = auth.restore_password('test@test.ru') 44 | ok, user = auth.complete_restore_password('test@test.ru', token, 'new_pwd') 45 | nok, _ = auth.check_auth(session) 46 | user['id'] = nil 47 | expected = {email = 'test@test.ru', is_active = true} 48 | test:is(ok, true, 'test_complete_restore_password_success password changed') 49 | test:is(nok, false, 'test_complete_restore_password_success session dropped') 50 | test:is_deeply(user, expected, 'test_complete_restore_password_success user returned') 51 | end 52 | 53 | function test_complete_restore_password_and_auth_success() 54 | local ok, token, user, expected, session 55 | ok, token = auth.restore_password('test@test.ru') 56 | ok, user = auth.complete_restore_password('test@test.ru', token, 'new_pwd') 57 | ok, user = auth.auth('test@test.ru', 'new_pwd') 58 | session = user['session'] 59 | user['id'] = nil 60 | user['session'] = nil 61 | expected = {email = 'test@test.ru', is_active = true} 62 | test:is(ok, true, 'test_complete_restore_password_and_auth_success user logged in') 63 | test:isstring(session, 'test_complete_restore_password_and_auth_success session returned') 64 | test:is_deeply(user, expected, 'test_complete_restore_password_and_auth_success user returned') 65 | end 66 | 67 | function test_restore_password_user_not_found() 68 | local got, expected 69 | got = {auth.restore_password('not_found@test.ru'), } 70 | expected = {response.error(error.USER_NOT_FOUND), } 71 | test:is_deeply(got, expected, 'test_restore_password_user_not_found') 72 | end 73 | 74 | function test_restore_password_user_not_active() 75 | local got, expected 76 | got = {auth.restore_password('not_active@test.ru'), } 77 | expected = {response.error(error.USER_NOT_ACTIVE), } 78 | test:is_deeply(got, expected, 'test_restore_password_user_not_active') 79 | end 80 | 81 | function test_complete_restore_password_user_not_found() 82 | local ok, token, id, session, got, expected 83 | ok, token = auth.restore_password('test@test.ru') 84 | 85 | -- TODO API METHOD FOR DELETING USER BY EMAIL ? 86 | id = get_id_by_email('test@test.ru') 87 | user_space:delete(id) 88 | 89 | got = {auth.complete_restore_password('test@test.ru', token, 'new_pwd'), } 90 | expected = {response.error(error.USER_NOT_FOUND), } 91 | test:is_deeply(got, expected, 'test_complete_restore_password_user_not_found') 92 | end 93 | 94 | function test_complete_restore_passsword_weak() 95 | local ok, token, id, session, got, expected 96 | ok, token = auth.restore_password('test@test.ru') 97 | 98 | got = {auth.complete_restore_password('test@test.ru', token, 'weak'), } 99 | expected = {response.error(error.WEAK_PASSWORD), } 100 | test:is_deeply(got, expected, 'test_complete_restore_passsword_weak 1') 101 | 102 | got = {auth.complete_restore_password('test@test.ru', token, '123123123'), } 103 | expected = {response.error(error.WEAK_PASSWORD), } 104 | test:is_deeply(got, expected, 'test_complete_restore_passsword_weak 2') 105 | 106 | got = {auth.complete_restore_password('test@test.ru', token, 'слабый'), } 107 | expected = {response.error(error.WEAK_PASSWORD), } 108 | test:is_deeply(got, expected, 'test_complete_restore_passsword_weak 3') 109 | end 110 | 111 | function test_complete_restore_password_user_not_active() 112 | local ok, token, id, session, got, expected 113 | ok, token = auth.restore_password('test@test.ru') 114 | 115 | -- TODO API METHOD FOR BAN USER BY EMAIL ? 116 | id = get_id_by_email('test@test.ru') 117 | user_space:update(id, {{'=', 4, false}}) 118 | 119 | got = {auth.complete_restore_password('test@test.ru', token, 'new_pwd'), } 120 | expected = {response.error(error.USER_NOT_ACTIVE), } 121 | test:is_deeply(got, expected, 'test_complete_restore_password_user_not_active') 122 | end 123 | 124 | function test_complete_restore_password_wrong_token() 125 | local ok, token, id, session, got, expected 126 | ok, token = auth.restore_password('test@test.ru') 127 | 128 | got = {auth.complete_restore_password('test@test.ru', 'wrong_password_token', 'new_pwd'), } 129 | expected = {response.error(error.WRONG_RESTORE_TOKEN), } 130 | test:is_deeply(got, expected, 'test_complete_restore_password_wrong_token') 131 | end 132 | 133 | function test_complete_restore_password_auth_with_old_password() 134 | local ok, token, user, got, expected 135 | ok, token = auth.restore_password('test@test.ru') 136 | ok, user = auth.complete_restore_password('test@test.ru', token, 'new_pwd') 137 | got = {auth.auth('test@test.ru', v.USER_PASSWORD), } 138 | expected = {response.error(error.WRONG_PASSWORD), } 139 | test:is_deeply(got, expected, 'test_complete_restore_password_auth_with_old_password') 140 | end 141 | 142 | function test_complete_restore_password_empty_token() 143 | local ok, token, got, expected 144 | ok, token = auth.restore_password('test@test.ru') 145 | got = {auth.complete_restore_password('test@test.ru', '', 'new_pwd'), } 146 | expected = {response.error(error.INVALID_PARAMS), } 147 | test:is_deeply(got, expected, 'test_complete_restore_password_empty_token') 148 | end 149 | 150 | exports.tests = { 151 | test_restore_password_success, 152 | test_complete_restore_password_success, 153 | test_complete_restore_password_and_auth_success, 154 | 155 | test_restore_password_user_not_found, 156 | test_restore_password_user_not_active, 157 | test_complete_restore_password_user_not_found, 158 | test_complete_restore_passsword_weak, 159 | test_complete_restore_password_user_not_active, 160 | test_complete_restore_password_wrong_token, 161 | test_complete_restore_password_auth_with_old_password, 162 | test_complete_restore_password_empty_token, 163 | } 164 | 165 | return exports 166 | -------------------------------------------------------------------------------- /test/case/social.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | local tap = require('tap') 3 | local fiber = require('fiber') 4 | local response = require('authman.response') 5 | local error = require('authman.error') 6 | local validator = require('authman.validator') 7 | local v = require('test.values') 8 | 9 | -- model configuration 10 | local config = validator.config(require('test.config')) 11 | local db = require('authman.db').configurate(config) 12 | local auth = require('authman').api(config) 13 | 14 | local test = tap.test('auth_test') 15 | 16 | function exports.setup() end 17 | 18 | function exports.before() end 19 | 20 | function exports.after() 21 | db.truncate_spaces() 22 | end 23 | 24 | function exports.teardown() end 25 | 26 | 27 | function test_social_auth_url_success() 28 | local ok, state, url 29 | 30 | state = 'somestate' 31 | ok, url = auth.social_auth_url('facebook', state) 32 | test:is(ok, true, 'test_social_auth_url_success fb ok') 33 | test:isstring(url, 'test_social_auth_url_success fb url') 34 | 35 | ok, url = auth.social_auth_url('vk') 36 | test:is(ok, true, 'test_social_auth_url_success vk ok') 37 | test:isstring(url, 'test_social_auth_url_success vk url') 38 | 39 | ok, url = auth.social_auth_url('google', state) 40 | test:is(ok, true, 'test_social_auth_url_success google ok') 41 | test:isstring(url, 'test_social_auth_url_success google url') 42 | end 43 | 44 | function test_social_auth_success() 45 | local ok, user,session, expected 46 | ok, user = auth.social_auth('vk', v.VALID_CODE) 47 | session = user['session'] 48 | user['id'] = nil 49 | user['session'] = nil 50 | 51 | expected = { 52 | email = v.USER_EMAIL, 53 | is_active = true, 54 | social = { 55 | provider = 'vk', 56 | social_id = v.SOCIAL_ID 57 | }, 58 | profile = { 59 | first_name = v.USER_FIRST_NAME, 60 | last_name = v.USER_LAST_NAME 61 | } 62 | } 63 | 64 | test:is(ok, true, 'test_social_auth_success user logged in') 65 | test:isstring(session, 'test_social_auth_success session returned') 66 | test:is_deeply(user, expected, 'test_social_auth_success user returned') 67 | end 68 | 69 | function test_social_auth_no_email_success() 70 | local ok, user,session, expected 71 | ok, user = auth.social_auth('vk', v.VALID_CODE_NO_EMAIL) 72 | session = user['session'] 73 | user['id'] = nil 74 | user['session'] = nil 75 | 76 | expected = { 77 | email = '', 78 | is_active = true, 79 | social = { 80 | provider = 'vk', 81 | social_id = v.SOCIAL_ID 82 | }, 83 | profile = { 84 | first_name = v.USER_FIRST_NAME, 85 | last_name = v.USER_LAST_NAME 86 | } 87 | } 88 | 89 | test:is(ok, true, 'test_social_auth_no_email_success user logged in') 90 | test:isstring(session, 'test_social_auth_no_email_success session returned') 91 | test:is_deeply(user, expected, 'test_social_auth_no_email_success user returned') 92 | end 93 | 94 | function test_social_auth_no_profile_success() 95 | local ok, user,session, expected 96 | ok, user = auth.social_auth('vk', v.VALID_CODE_NO_PROFILE) 97 | session = user['session'] 98 | user['id'] = nil 99 | user['session'] = nil 100 | 101 | expected = { 102 | email = v.USER_EMAIL, 103 | is_active = true, 104 | social = { 105 | provider = 'vk', 106 | social_id = v.SOCIAL_ID 107 | }, 108 | profile = {} 109 | } 110 | 111 | test:is(ok, true, 'test_social_auth_no_profile_success user logged in') 112 | test:isstring(session, 'test_social_auth_no_profile_success session returned') 113 | test:is_deeply(user, expected, 'test_social_auth_no_profile_success user returned') 114 | end 115 | 116 | function test_check_auth_social_success() 117 | local ok, user,session, expected 118 | ok, user = auth.social_auth('vk', v.VALID_CODE) 119 | session = user['session'] 120 | 121 | ok, user = auth.check_auth(session) 122 | session = user['session'] 123 | user['id'] = nil 124 | user['session'] = nil 125 | 126 | expected = { 127 | email = v.USER_EMAIL, 128 | is_active = true, 129 | social = { 130 | provider = 'vk', 131 | social_id = v.SOCIAL_ID 132 | }, 133 | profile = { 134 | first_name = v.USER_FIRST_NAME, 135 | last_name = v.USER_LAST_NAME 136 | } 137 | } 138 | 139 | test:is(ok, true, 'test_check_auth_social_success user logged in') 140 | test:isstring(session, 'test_check_auth_social_success session returned') 141 | test:is_deeply(user, expected, 'test_check_auth_social_success user returned') 142 | end 143 | 144 | function test_drop_social_session_success() 145 | local ok, user,session, expected, deleted, got 146 | ok, user = auth.social_auth('vk', v.VALID_CODE) 147 | session = user['session'] 148 | ok, deleted = auth.drop_session(session) 149 | test:is(ok, true, 'test_drop_session_success session droped') 150 | 151 | got = {auth.check_auth(session), } 152 | expected = {response.error(error.WRONG_SESSION_SIGN), } 153 | test:is_deeply(got, expected, 'test_drop_social_session_success wrong sign') 154 | end 155 | 156 | function test_check_auth_update_social_session_success() 157 | local ok, user, got, expected, first_session, second_session 158 | ok, user = auth.social_auth('vk', v.VALID_CODE) 159 | first_session = user['session'] 160 | 161 | fiber.sleep(config.social_check_time) 162 | 163 | ok, user = auth.check_auth(first_session) 164 | test:is(ok, true, 'test_check_auth_update_social_session_success session updated') 165 | 166 | second_session = user['session'] 167 | test:isstring(second_session, 'test_check_auth_update_social_session_success session returned') 168 | test:isnt(first_session, second_session, 'test_check_auth_update_social_session_success new session') 169 | end 170 | 171 | function test_check_auth_expired_social_session() 172 | local ok, user, got, expected, session 173 | ok, user = auth.social_auth('vk', v.VALID_CODE) 174 | session = user['session'] 175 | 176 | fiber.sleep(config.session_lifetime) 177 | 178 | got = {auth.check_auth(session), } 179 | expected = {response.error(error.NOT_AUTHENTICATED), } 180 | test:is_deeply(got, expected, 'test_check_auth_expired_social_session') 181 | end 182 | 183 | 184 | function test_social_auth_url_invalid_provider() 185 | local got, expected 186 | 187 | got = {auth.social_auth_url('invalid'), } 188 | expected = {response.error(error.WRONG_PROVIDER) } 189 | test:is_deeply(got, expected, 'test_social_auth_url_invalid_provider') 190 | end 191 | 192 | function test_social_auth_invalid_provider() 193 | local got, expected 194 | 195 | got = {auth.social_auth('invalid', 'some code'), } 196 | expected = {response.error(error.WRONG_PROVIDER) } 197 | test:is_deeply(got, expected, 'test_social_auth_url_invalid_provider') 198 | end 199 | 200 | function test_social_auth_invalid_code() 201 | local got, expected 202 | 203 | got = {auth.social_auth('vk', 'invalid code'), } 204 | expected = {response.error(error.WRONG_AUTH_CODE) } 205 | test:is_deeply(got, expected, 'test_social_auth_invalid_code') 206 | end 207 | 208 | function test_social_auth_invalid_auth() 209 | local got, expected 210 | 211 | got = {auth.social_auth('vk', v.INVALID_CODE_TOKEN), } 212 | expected = {response.error(error.SOCIAL_AUTH_ERROR) } 213 | test:is_deeply(got, expected, 'test_social_auth_invalid_auth') 214 | end 215 | 216 | 217 | exports.tests = { 218 | test_social_auth_url_success, 219 | test_social_auth_success, 220 | test_social_auth_no_email_success, 221 | test_social_auth_no_profile_success, 222 | test_drop_social_session_success, 223 | test_check_auth_social_success, 224 | test_check_auth_update_social_session_success, 225 | 226 | test_check_auth_expired_social_session, 227 | test_social_auth_url_invalid_provider, 228 | test_social_auth_invalid_provider, 229 | test_social_auth_invalid_code, 230 | test_social_auth_invalid_auth, 231 | } 232 | 233 | return exports -------------------------------------------------------------------------------- /test/config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | port = 3301, 3 | test_database_dir = '/tmp/tarantool-authman/test/test_db', 4 | 5 | activation_secret = 'ehbgrTUHIJ7689fyvg', 6 | session_secret = 'aswfWERVefver324efv', 7 | restore_secret = 'ybhinjTRCFYVGUHB5678jh', 8 | session_lifetime = 3, 9 | session_update_timedelta = 2, 10 | social_check_time = 2, 11 | 12 | password_strength = 'easy', 13 | 14 | request_timeout = 3, 15 | oauth_max_apps = 10, 16 | 17 | facebook = { 18 | client_id = '1813230128941062', 19 | client_secret = 'some secret here', 20 | redirect_uri='http://localhost:8000/', 21 | }, 22 | google = { 23 | client_id = '495340653331-3gmtvon6tc1o61ajn5piek6jgi0p2o47.apps.googleusercontent.com', 24 | client_secret = 'some secret here', 25 | redirect_uri='http://localhost:8000/', 26 | }, 27 | vk = { 28 | client_id = '5873775', 29 | client_secret = 'some secret here', 30 | redirect_uri='http://localhost:8000/', 31 | }, 32 | } -------------------------------------------------------------------------------- /test/mock/authman/utils/http.lua: -------------------------------------------------------------------------------- 1 | local http = {} 2 | 3 | local json = require('json') 4 | local v = require('test.values') 5 | 6 | local function response(code, body) 7 | return { 8 | status = code, 9 | body = json.encode(body) 10 | } 11 | end 12 | 13 | function http.api(config) 14 | 15 | local api = {} 16 | 17 | function api.request(method, url, params, param_values) 18 | 19 | if string.match(url, 'vk.com') ~= nil then 20 | if string.match(params, 'code=') then 21 | if param_values['code'] == v.VALID_CODE then 22 | return response(v.HTTP_OK, { 23 | email = v.USER_EMAIL, 24 | access_token = v.VALID_TOKEN 25 | }) 26 | elseif param_values['code'] == v.VALID_CODE_NO_EMAIL then 27 | return response(v.HTTP_OK, { 28 | access_token = v.VALID_TOKEN 29 | }) 30 | elseif param_values['code'] == v.VALID_CODE_NO_PROFILE then 31 | return response(v.HTTP_OK, { 32 | email = v.USER_EMAIL, 33 | access_token = v.VALID_TOKEN_NO_PROFILE 34 | }) 35 | elseif param_values['code'] == v.INVALID_CODE_TOKEN then 36 | return response(v.HTTP_OK, { 37 | email = v.USER_EMAIL, 38 | access_token = v.INVALID_TOKEN 39 | }) 40 | elseif param_values['code'] == v.INVALID_CODE then 41 | return response(v.HTTP_401, {}) 42 | end 43 | 44 | elseif string.match(params, 'token=') then 45 | if param_values['token'] == v.VALID_TOKEN then 46 | return response(v.HTTP_OK, { 47 | response = { 48 | {id=v.SOCIAL_ID, first_name=v.USER_FIRST_NAME, last_name=v.USER_LAST_NAME}, 49 | } 50 | }) 51 | elseif param_values['token'] == v.VALID_TOKEN_NO_PROFILE then 52 | return response(v.HTTP_OK, { 53 | response = { 54 | {id=v.SOCIAL_ID}, 55 | } 56 | }) 57 | elseif param_values['token'] == v.INVALID_TOKEN then 58 | return response(v.HTTP_401, {}) 59 | end 60 | end 61 | 62 | end 63 | end 64 | 65 | return api 66 | end 67 | 68 | 69 | return http -------------------------------------------------------------------------------- /test/values.lua: -------------------------------------------------------------------------------- 1 | return { 2 | HTTP_OK = 200, 3 | HTTP_401 = 401, 4 | 5 | USER_EMAIL = 'test@test.ru', 6 | USER_PASSWORD = '123Asd', 7 | 8 | USER_FIRST_NAME = 'first_name', 9 | USER_LAST_NAME = 'last_name', 10 | SOCIAL_ID = '123123', 11 | 12 | VALID_CODE = 'valid_code', 13 | VALID_CODE_NO_EMAIL = 'valid_code_no_email', 14 | VALID_CODE_NO_PROFILE = 'valid_code_no_profile', 15 | 16 | VALID_TOKEN = 'valid_token', 17 | VALID_TOKEN_NO_EMAIL = 'valid_token_no_email', 18 | VALID_TOKEN_NO_PROFILE = 'valid_token_no_profile', 19 | 20 | INVALID_CODE_TOKEN = 'invalid_code_token', 21 | INVALID_TOKEN = 'invalid_token', 22 | 23 | OAUTH_APP_NAME = 'app name', 24 | OAUTH_VALID_APP_TYPES = {'server', 'browser', 'native', 'mobile'}, 25 | OAUTH_CONSUMER_REDIRECT_URLS = 'https://test.ru/test1?test=1 https://test.ru/test2?test=2', 26 | OAUTH_CONSUMER_REDIRECT_URL = 'https://test.ru/test1?test=1', 27 | OAUTH_CODE = 'oauth_code', 28 | OAUTH_CONSUMER_KEY = 'oauth_consumer_key', 29 | OAUTH_SCOPE = 'oauth_scope', 30 | OAUTH_STATE = 'oauth_state', 31 | OAUTH_EXPIRES_IN = 1514452737, 32 | OAUTH_CREATED_AT = 1514452137, 33 | OAUTH_CODE_CHALLENGE = 'oauth_code_challenge', 34 | OAUTH_CODE_CHALLENGE_METHOD = 'oauth_code_challenge_method', 35 | OAUTH_ACCESS_TOKEN = 'oauth_access_token', 36 | OAUTH_REFRESH_TOKEN = 'oauth_refresh_token', 37 | OAUTH_RESOURCE_OWNER = 'resource_owner' 38 | } 39 | --------------------------------------------------------------------------------