authify-api 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1ad42a62cd052be494876ad2448c87fadc49d779
4
- data.tar.gz: 63e6a369a12ea65833c62330d6f1df2a25ed1473
3
+ metadata.gz: 3fca257750d3b6838d69bf82accc2509efedec36
4
+ data.tar.gz: 3e7bffd8e96c2eaa0fac52f0f7cbc7511e0d8a4d
5
5
  SHA512:
6
- metadata.gz: c17f797d481991ff2237c8c9d16b965670094394609a9b0e20fd1745ca6b84de4610a843ee8010bc999f4815975d0cc12aeb678966232f8fc0137b50c2835d80
7
- data.tar.gz: 0eb621c92eaa49877952c8b3cf03741056af7084170c05af708ba0d921dbc4f62d741bdddefa5cad85975a4457ba593a494302324abb62478cd20ff470faf31e
6
+ metadata.gz: 0f6224388bec7ae3b38a11c3447950ef5bec48b9505aaade543c9cd517cb643897189bfc077df39fc19a103ab13276b7edb53756981a800b95a4d3b0d43875fe
7
+ data.tar.gz: 1169b5cd8efe0660d3ff7fbf533e1dd086dcea7bc30e7824fadcd80dfeeab3c5daff5267b8a669a1ccbcb1077c2763bc59006539c2cdabcecc8e6cd198b2b69f
data/README.md CHANGED
@@ -22,7 +22,9 @@ Nearly all API endpoints available via Authify implement the [{json:api}](http:/
22
22
  * `GET /jwt/key` - Returns Content Type: `application/json`. This endpoint returns a JSON Object with the key `data` whose value is a PEM-encoded ECDSA public key, which should be used to verify the signature made by the Authify service.
23
23
  * `GET /jwt/meta` - Returns Content Type: `application/json`. This endpoint returns a JSON Object with the keys `algorithm`, `issuer`, and `expiration` that describe the kind of JWTs produced by this service.
24
24
  * `POST /jwt/token` - Returns (and only accepts) Content Type: `application/json`. This endpoint is used to obtain a [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token). This endpoint expects a JSON Object with either the keys `access_key` and `secret_key` _OR_ `email` and `password`. There is no firm requirement to use either pair for any particular purpose, but for scenarios where the credentials may be stored, the `access_key` and `secret_key` may be used since those can easily be revoked if necessary. Upon successful authentication, the endpoint provides a JSON Object with the key `jwt` and a signed JWT. There should be nothing highly sensitive embedded in the JWT. The JWT defaults to expiring every 15 minutes.
25
- * `POST /registration/signup` - Returns (and only accepts) Content Type: `application/json`. This endpoint is used to signup for an account with Authify. This endpoint expects a JSON Object, requiring the keys `email` and `password`, with `name` and `via` being optional. If `via` is provided, then it must be a JSON Object with the keys `provider` and `uid`, otherwise it will be ignored. The `via` key is used to add an alternate identity (meaning they logged-in through an integration, like Github). This endpoint returns a JSON Object with the keys `id`, `email`, and `jwt` on success.
25
+ * `POST /registration/signup` - Returns (and only accepts) Content Type: `application/json`. This endpoint is used to signup for an account with Authify. This endpoint expects a JSON Object, requiring the keys `email` and `password`, with `name` and `via` being optional. If `via` is provided, then it must be a JSON Object with the keys `provider` and `uid`, otherwise it will be ignored. The `via` key is used to add an alternate identity (meaning they logged-in through an integration, like Github), and is only trusted from trusted delegates (meaning it will be ignored for anonymous calls to this endpoint). This endpoint returns a JSON Object with the keys `id`, `email`, and `verified`, on success. If the user is registered by a trusted delegate *and* `via` options were provided, the users is implicitly trusted and a `jwt` key will also be provided for authentication.
26
+ * `POST /registration/verify` - Returns (and only accepts) Content Type: `application/json`. This endpoint is used to verify a registered user's email address. This endpoint expects a JSON Object, requiring the keys `email`, `password`, and `token`. This endpoint returns a JSON Object with the keys `id`, `email`, `verified`, and `jwt` on success.
27
+ * `POST /registration/forgot_password` - Returns (and only accepts) Content Type: `application/json`. This endpoint serves two related purposes: it is used to trigger resetting a forgotten (or non-existent) password and it is used to actually set the value of a user's password. The difference in which operation is performed is based on the POST data. When provided a JSON Object with only the key `email`, the endpoint sends the user an email with a verification token, returning an empty JSON Object as a result. When provided a JSON Object with the keys `email`, `password`, and `token`, the endpoint verifies that the token matches, then sets the user's password, returning a JSON Object with the keys `id`, `email`, `verified`, and `jwt` on success.
26
28
 
27
29
  All other endpoints adhere to the {json:api} specification and can be found at the following base paths:
28
30
 
@@ -60,7 +62,7 @@ The Authify API services supports the following configuration settings, managed
60
62
 
61
63
  * `AUTHIFY_DB_URL` - The URL used by [ActiveRecord](http://guides.rubyonrails.org/configuring.html#configuring-a-database) to connect to the database. Currently supports `mysql2://` or `sqlite3://` URLs, though any driver supported by ActiveRecord should work if the required gems are installed. Defaults to `mysql2://root@localhost:3306/authifydb`.
62
64
  * `AUTHIFY_PUBKEY_PATH` - The path on the filesystem to the PEM-encoded, public ECDSA key. Defaults to `~/.authify/ssl/public.pem`.
63
- * `AUTHIFY_PRIVKEY_PATH` - The path on the filesystem to the PEM-encoded, private ECDSA key. Currently, Authify only supports an [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) keys. Options include using a `secp521r1` curve and the [SHA-512](https://en.wikipedia.org/wiki/SHA-2) hashing algorithm (called `ES512`), a `secp384r1` curve and the SHA-384 hashing algorithm (called `ES384`), or a `prime256v1` curve and the SHA-256 hashing algorithm (called `ES256`). See `AUTHIFY_JWT_ALGORITHM` below for information on how to configure Authify's algorithm to match the public and private keys you provide. The keys you specify **must** match the ECDSA algortihm and curve used to create them.
65
+ * `AUTHIFY_PRIVKEY_PATH` - The path on the filesystem to the PEM-encoded, private ECDSA key. Currently, Authify only supports an [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) keys. Options include using a `secp521r1` curve and the [SHA-512](https://en.wikipedia.org/wiki/SHA-2) hashing algorithm (called `ES512`), a `secp384r1` curve and the SHA-384 hashing algorithm (called `ES384`), or a `prime256v1` curve and the SHA-256 hashing algorithm (called `ES256`). See `AUTHIFY_JWT_ALGORITHM` below for information on how to configure Authify's algorithm to match the public and private keys you provide. The keys you specify **must** match the ECDSA algortihm and curve used to create them.
64
66
  * `AUTHIFY_JWT_ISSUER` - The name of the issuer ([iss field](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields)) used when creating the JWT. This **must** match on any service that verifies the JWT (meaning any service relying on Authify for authentication), and it **must** be the same for all services that integrate with Authify.
65
67
  * `AUTHIFY_JWT_ALGORITHM` - The name of the [JWA](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40) algorithm to use when loading keys and creating or verifying JWT signatures. Valid values are `ES256`, `ES384`, or `ES512`. Defaults to `ES512`. This **must** match the curve and algorithm used to produce the public and private keys found at `AUTHIFY_PUBKEY_PATH` and `AUTHIFY_PRIVKEY_PATH`, respectively. Note that the curves `prime256v1` (also called NIST P-256) used by `ES256` and `secp384r1` (also called NIST P-384) used by `ES384`, while offering a wider range of compatible SSL libraries, are described as unsafe on [SafeCurves](https://safecurves.cr.yp.to/) for several reasons described there.
66
68
  * `AUTHIFY_JWT_EXPIRATION` - How long should a JWT be valid (in minutes). Defaults to 15. Too small of a value will mean a lot more requests to the API; too high increases the possibility of viable keys being captured.
@@ -108,10 +110,6 @@ curl \
108
110
  "name": "Some User",
109
111
  "email": "someuser@mycompany.com",
110
112
  "password": "b@d!dea",
111
- "via": {
112
- "provider": "github",
113
- "uid": "1234567"
114
- }
115
113
  }' \
116
114
  https://auth.mycompany.com/registration/signup
117
115
  ```
@@ -122,11 +120,37 @@ This will return JSON similar to the following:
122
120
  {
123
121
  "id": 172,
124
122
  "email": "someuser@mycompany.com",
123
+ "verified": false
124
+ }
125
+ ```
126
+
127
+ As you can see, Authify is stating that while you have registered a user, their email address has not been verified. They should receive an email containing a one-time verification token, valid for an hour. Verify the email by POSTing something similar to:
128
+
129
+ ```shell
130
+ curl \
131
+ -H 'Content-Type: application/json' \
132
+ -H 'Accept: application/json' \
133
+ --data \
134
+ '{
135
+ "email": "someuser@mycompany.com",
136
+ "password": "b@d!dea",
137
+ "token": "c7994995c89039ab"
138
+ }' \
139
+ https://auth.mycompany.com/registration/verify
140
+ ```
141
+
142
+ This will return JSON similar to the following:
143
+
144
+ ```javascript
145
+ {
146
+ "id": 172,
147
+ "email": "someuser@mycompany.com",
148
+ "verified": true,
125
149
  "jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJleHAiOjE0ODY0ODcyODcsImlhdCI6MTQ4NjQ4MzY4NywiaXNzIjoiTXkgQXdlc29tZSBDb21wYW55IEluYy4iLCJzY29wZXMiOlsidXNlcl9hY2Nlc3MiXSwidXNlciI6eyJ1c2VybmFtZSI6ImZvb0BiYXIuY29tIiwidWlkIjoyLCJvcmdhbml6YXRpb25zIjpbXSwiZ3JvdXBzIjpbXX19.AWfPpKX9mP03Djz3-LMneJdEVsXQm_4GOPVCdkfiiBeIR4pVLKTVrNoNdlNgSEkZEeUw1RPsVxpAR7wDgB4cNcYiAP3fNaD8OPyWfOQAV0lTvDUSH3YU39cZAVwvbX9HleOHBLrFGBbui5wSvfi7WZZlH808psiuUAVhBOe7mfrNiHGB"
126
150
  }
127
151
  ```
128
152
 
129
- You'll need the JWT (found at key `jwt`) for the next step.
153
+ The user is now verified. You'll need the JWT (found at key `jwt`) for the next step.
130
154
 
131
155
  #### Create an API key set
132
156
 
@@ -187,7 +211,7 @@ curl \
187
211
  -H 'Accept: application/json' \
188
212
  -H 'Content-Type: application/json' \
189
213
  --data \
190
- '{
214
+ '{
191
215
  "access_key": "5f4abd1c6423ef02d1ec42e1cddaf5f8",
192
216
  "secret_key": "fb97aa7d4e48f3e4bbb2930161a423fa8308393426c3612940da03f22cf36879"
193
217
  }' \
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ end
12
12
  RSpec::Core::RakeTask.new(:spec)
13
13
  RuboCop::RakeTask.new(:rubocop)
14
14
 
15
- task default: %i(spec rubocop)
15
+ task default: %i[spec rubocop]
16
16
 
17
17
  desc 'Start the demo using `rackup`'
18
18
  task :start do
@@ -18,7 +18,7 @@ module Authify
18
18
  Models::APIKey.all
19
19
  end
20
20
 
21
- show(roles: %i(myself admin)) do
21
+ show(roles: %i[myself admin]) do
22
22
  last_modified resource.updated_at
23
23
  next resource, exclude: [:secret_key]
24
24
  end
@@ -31,7 +31,7 @@ module Authify
31
31
  next key.id, key
32
32
  end
33
33
 
34
- destroy(roles: %i(myself admin)) do
34
+ destroy(roles: %i[myself admin]) do
35
35
  resource.destroy
36
36
  end
37
37
 
@@ -40,7 +40,7 @@ module Authify
40
40
  end
41
41
 
42
42
  has_one :user do
43
- pluck(roles: %i(myself admin)) do
43
+ pluck(roles: %i[myself admin]) do
44
44
  resource.user
45
45
  end
46
46
  end
@@ -19,7 +19,7 @@ module Authify
19
19
  Models::Group.all
20
20
  end
21
21
 
22
- show(roles: %i(admin owner)) do
22
+ show(roles: %i[admin owner]) do
23
23
  last_modified resource.updated_at
24
24
  next resource
25
25
  end
@@ -30,7 +30,7 @@ module Authify
30
30
  next g
31
31
  end
32
32
 
33
- destroy(roles: %i(admin owner)) do
33
+ destroy(roles: %i[admin owner]) do
34
34
  resource.destroy
35
35
  end
36
36
 
@@ -39,23 +39,23 @@ module Authify
39
39
  end
40
40
 
41
41
  has_many :users do
42
- fetch(roles: %i(admin owner)) do
42
+ fetch(roles: %i[admin owner]) do
43
43
  resource.users
44
44
  end
45
45
 
46
- replace(roles: %i(admin owner)) do |rios|
46
+ replace(roles: %i[admin owner]) do |rios|
47
47
  refs = rios.map { |attrs| Models::User.find(attrs) }
48
48
  resource.users = refs
49
49
  resource.save
50
50
  end
51
51
 
52
- merge(roles: %i(admin owner)) do |rios|
52
+ merge(roles: %i[admin owner]) do |rios|
53
53
  refs = rios.map { |attrs| Models::User.find(attrs) }
54
54
  resource.users << refs
55
55
  resource.save
56
56
  end
57
57
 
58
- subtract(roles: %i(admin owner)) do |rios|
58
+ subtract(roles: %i[admin owner]) do |rios|
59
59
  refs = rios.map { |attrs| Models::User.find(attrs) }
60
60
  # This only removes the linkage, not the actual users
61
61
  resource.users.delete(refs)
@@ -14,11 +14,11 @@ module Authify
14
14
  end
15
15
  end
16
16
 
17
- index(roles: %i(admin trusted)) do
17
+ index(roles: %i[admin trusted]) do
18
18
  Models::Identity.all
19
19
  end
20
20
 
21
- show(roles: %i(myself admin trusted)) do
21
+ show(roles: %i[myself admin trusted]) do
22
22
  last_modified resource.updated_at
23
23
  next resource
24
24
  end
@@ -30,7 +30,7 @@ module Authify
30
30
  next ident.id, ident
31
31
  end
32
32
 
33
- destroy(roles: %i(myself admin)) do
33
+ destroy(roles: %i[myself admin]) do
34
34
  resource.destroy
35
35
  end
36
36
 
@@ -39,7 +39,7 @@ module Authify
39
39
  end
40
40
 
41
41
  has_one :user do
42
- pluck(roles: %i(myself admin trusted)) do
42
+ pluck(roles: %i[myself admin trusted]) do
43
43
  resource.user
44
44
  end
45
45
  end
@@ -15,7 +15,7 @@ module Authify
15
15
  end
16
16
 
17
17
  def modifiable_fields
18
- %i(
18
+ %i[
19
19
  name
20
20
  public_email
21
21
  gravatar_email
@@ -23,7 +23,7 @@ module Authify
23
23
  description
24
24
  url
25
25
  location
26
- )
26
+ ]
27
27
  end
28
28
 
29
29
  def filtered_attributes(attributes)
@@ -41,7 +41,7 @@ module Authify
41
41
  end
42
42
  end
43
43
 
44
- index(roles: %i(admin user)) do
44
+ index(roles: %i[admin user]) do
45
45
  Models::Organization.includes(:users, :groups, :admins)
46
46
  end
47
47
 
@@ -72,29 +72,29 @@ module Authify
72
72
  next o.id, o
73
73
  end
74
74
 
75
- update(roles: %i(owner admin)) do |attrs|
75
+ update(roles: %i[owner admin]) do |attrs|
76
76
  resource.update filtered_attributes(attrs)
77
77
  next resource
78
78
  end
79
79
 
80
- destroy(roles: %i(owner admin)) do
80
+ destroy(roles: %i[owner admin]) do
81
81
  resource.destroy
82
82
  end
83
83
 
84
84
  has_many :users do
85
- fetch(roles: %i(owner admin member)) do
85
+ fetch(roles: %i[owner admin member]) do
86
86
  resource.users
87
87
  end
88
88
  end
89
89
 
90
90
  has_many :admins do
91
- fetch(roles: %i(owner admin member)) do
91
+ fetch(roles: %i[owner admin member]) do
92
92
  resource.admins
93
93
  end
94
94
  end
95
95
 
96
96
  has_many :groups do
97
- fetch(roles: %i(owner admin member)) do
97
+ fetch(roles: %i[owner admin member]) do
98
98
  resource.groups
99
99
  end
100
100
  end
@@ -14,7 +14,7 @@ module Authify
14
14
  end
15
15
 
16
16
  def modifiable_fields
17
- %i(full_name email).tap do |a|
17
+ %i[full_name email].tap do |a|
18
18
  a << :admin if role.include?(:admin)
19
19
  end
20
20
  end
@@ -34,11 +34,11 @@ module Authify
34
34
  end
35
35
  end
36
36
 
37
- index(roles: %i(user trusted)) do
37
+ index(roles: %i[user trusted]) do
38
38
  Models::User.all
39
39
  end
40
40
 
41
- show(roles: %i(user trusted)) do
41
+ show(roles: %i[user trusted]) do
42
42
  last_modified resource.updated_at
43
43
  next resource
44
44
  end
@@ -49,7 +49,7 @@ module Authify
49
49
  next user
50
50
  end
51
51
 
52
- update(roles: %i(admin myself)) do |attrs|
52
+ update(roles: %i[admin myself]) do |attrs|
53
53
  # Necessary because #password= is overridden for Models::User
54
54
  new_pass = attrs[:password] if attrs && attrs.key?(:password)
55
55
  resource.update filtered_attributes(attrs)
@@ -63,16 +63,16 @@ module Authify
63
63
  end
64
64
 
65
65
  has_many :apikeys do
66
- fetch(roles: %i(myself admin)) do
66
+ fetch(roles: %i[myself admin]) do
67
67
  resource.apikeys
68
68
  end
69
69
 
70
- clear(roles: %i(myself admin)) do
70
+ clear(roles: %i[myself admin]) do
71
71
  resource.apikeys.destroy_all
72
72
  resource.save
73
73
  end
74
74
 
75
- subtract(roles: %i(myself admin)) do |rios|
75
+ subtract(roles: %i[myself admin]) do |rios|
76
76
  refs = rios.map { |attrs| Models::APIKey.find(attrs) }
77
77
  # This actually calls #destroy on the keys (we don't need orphaned keys)
78
78
  resource.apikeys.destroy(refs)
@@ -81,11 +81,11 @@ module Authify
81
81
  end
82
82
 
83
83
  has_many :identities do
84
- fetch(roles: %i(myself admin trusted)) do
84
+ fetch(roles: %i[myself admin trusted]) do
85
85
  resource.identities
86
86
  end
87
87
 
88
- clear(roles: %i(myself admin)) do
88
+ clear(roles: %i[myself admin]) do
89
89
  resource.identities.destroy_all
90
90
  resource.save
91
91
  end
@@ -96,7 +96,7 @@ module Authify
96
96
  resource.save
97
97
  end
98
98
 
99
- subtract(roles: %i(myself admin)) do |rios|
99
+ subtract(roles: %i[myself admin]) do |rios|
100
100
  refs = rios.map { |attrs| Models::Identity.find(attrs) }
101
101
  resource.identities.destroy(refs)
102
102
  resource.save
@@ -104,13 +104,13 @@ module Authify
104
104
  end
105
105
 
106
106
  has_many :organizations do
107
- fetch(roles: %i(user myself admin)) do
107
+ fetch(roles: %i[user myself admin]) do
108
108
  resource.organizations
109
109
  end
110
110
  end
111
111
 
112
112
  has_many :groups do
113
- fetch(roles: %i(myself admin)) do
113
+ fetch(roles: %i[myself admin]) do
114
114
  resource.groups
115
115
  end
116
116
  end
@@ -12,11 +12,11 @@ module Authify
12
12
  before '*' do
13
13
  content_type 'application/json'
14
14
  headers 'Access-Control-Allow-Origin' => '*',
15
- 'Access-Control-Allow-Methods' => %w(
15
+ 'Access-Control-Allow-Methods' => %w[
16
16
  OPTIONS
17
17
  GET
18
18
  POST
19
- )
19
+ ]
20
20
 
21
21
  begin
22
22
  unless request.get? || request.options?
@@ -12,11 +12,11 @@ module Authify
12
12
  before '*' do
13
13
  content_type 'application/json'
14
14
  headers 'Access-Control-Allow-Origin' => '*',
15
- 'Access-Control-Allow-Methods' => %w(
15
+ 'Access-Control-Allow-Methods' => %w[
16
16
  OPTIONS
17
17
  GET
18
18
  POST
19
- )
19
+ ]
20
20
 
21
21
  begin
22
22
  unless request.get? || request.options?
@@ -63,6 +63,31 @@ module Authify
63
63
  response.to_json
64
64
  end
65
65
 
66
+ post '/forgot_password' do
67
+ email = @parsed_body[:email]
68
+ token = @parsed_body[:token]
69
+ halt(200, '{}') unless Models::User.exists?(email: email)
70
+ halt(403, 'Missing Parameters') unless email
71
+
72
+ found_user = Models::User.find_by_email(email)
73
+ if token && @parsed_body[:password] && found_user.verify(token)
74
+ found_user.verified = true
75
+ found_user.password = @parsed_body[:password]
76
+ found_user.save
77
+ {
78
+ id: found_user.id,
79
+ email: found_user.email,
80
+ verified: found_user.verified?,
81
+ jwt: jwt_token(found_user)
82
+ }.to_json
83
+ else
84
+ found_user.verified = false
85
+ found_user.set_verification_token!
86
+ found_user.save
87
+ halt(200, '{}')
88
+ end
89
+ end
90
+
66
91
  post '/verify' do
67
92
  email = @parsed_body[:email]
68
93
  password = @parsed_body[:password]
@@ -2,7 +2,7 @@ module Authify
2
2
  module API
3
3
  VERSION = [
4
4
  0, # Major
5
- 2, # Minor
5
+ 3, # Minor
6
6
  0 # Patch
7
7
  ].join('.')
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authify-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Gnagy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-29 00:00:00.000000000 Z
11
+ date: 2017-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: authify-core