authify-api 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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