/
guardian.ex
785 lines (610 loc) · 25.3 KB
/
guardian.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
defmodule Guardian do
@moduledoc ~S"""
Guardian provides a singular interface for authentication in Elixir applications
that is `token` based.
Tokens should be:
* tamper proof
* include a payload (claims)
JWT tokens (the default) fit this description.
When using Guardian, you'll need an implementation module.
```elixir
defmodule MyApp.Guardian do
use Guardian, otp_app: :my_app
def subject_for_token(resource, _claims), do: {:ok, to_string(resource.id)}
def resource_from_claims(claims) do
find_me_a_resource(claims["sub"]) # {:ok, resource} or {:error, reason}
end
end
```
This module is what you will use to interact with tokens in your application.
When you `use` Guardian, the `:otp_app` option is required.
Any other option provided will be merged with the configuration in the config
files.
The Guardian module contains some generated functions and some callbacks.
## Generated function
#### `default_token_type()`
Overridable.
Provides the default token type for the token - `"access"`
Token types allow a developer to mark a token as having a particular purpose.
Different types of tokens can then be used specifically in your app.
Types may include (but are not limited to):
* `"access"`
* `"refresh"`
Access tokens should be short lived and are used to access resources on your API.
Refresh tokens should be longer lived and whose only purpose is to exchange
for a shorter lived access token.
To specify the type of token, use the `:token_type` option in
the `encode_and_sign` function.
Token type is encoded into the token in the `"typ"` field.
Return - a string.
#### `peek(token)`
Inspect a tokens payload. Note that this function does no verification.
Return - a map including the `:claims` key.
#### `config()`, `config(key, default \\ nil)`
Without argument `config` will return the full configuration Keyword list.
When given a `key` and optionally a default, `config` will fetch a resolved value
contained in the key.
See `Guardian.Config.resolve_value/1`
#### `encode_and_sign(resource, claims \\ %{}, opts \\ [])`
Creates a signed token.
Arguments:
* `resource` - The resource to represent in the token (i.e. the user)
* `claims` - Any custom claims that you want to use in your token
* `opts` - Options for the token module and callbacks
For more information on options see the documentation for your token module.
```elixir
# Provide a token using the defaults including the default_token_type
{:ok, token, full_claims} = MyApp.Guardian.encode_and_sign(user)
# Provide a token including custom claims
{:ok, token, full_claims} = MyApp.Guardian.encode_and_sign(user, %{some: "claim"})
# Provide a token including custom claims and a different token type/ttl
{:ok, token, full_claims} =
MyApp.Guardian.encode_and_sign(user, %{some: "claim"}, token_type: "refresh" ttl: {4, :weeks})
```
The `encode_and_sign` function calls a number of callbacks on
your implementation module. See `Guardian.encode_and_sign/4`
#### `decode_and_verify(token, claims_to_check \\ %{}, opts \\ [])`
Decodes a token and verifies the claims are valid.
Arguments:
* `token` - The token to decode
* `claims_to_check` - A map of the literal claims that should be matched.
If any of the claims do not literally match
verification fails
* `opts` - The options to pass to the token module and callbacks
Callbacks:
`decode_and_verify` calls a number of callbacks on your implementation module,
See `Guardian.decode_and_verify/4`
```elixir
# Decode and verify using the defaults
{:ok, claims} = MyApp.Guardian.decode_and_verify(token)
# Decode and verify with literal claims check.
# If the claims in the token do not match those given verification will fail
{:ok, claims} = MyApp.Guardian.decode_and_verify(token, %{match: "claim"})
# Decode and verify with literal claims check and options.
# Options are passed to your token module and callbacks
{:ok, claims} = MyApp.Guardian.decode_and_verify(token, %{match: "claim"}, some: "secret")
```
#### `revoke(token, opts \\ [])`
Revoke a token.
*Note:* this is entirely dependent on your token module and implementation
callbacks.
```elixir
{:ok, claims} = MyApp.Guardian.revoke(token, some: "option")
```
#### `refresh(token, opts \\ [])`
Refreshes the time on a token. This is used to re-issue a token with
essentially the same claims but with a different expiry.
Tokens are verified before performing the refresh to ensure
only valid tokens may be refreshed.
Arguments:
* `token` - The old token to refresh
* `opts` - Options to pass to the Implementation Module and callbacks
Options:
* `:ttl` - The new ttl. If not specified the default will be used.
```elixir
{:ok, {old_token, old_claims}, {new_token, new_claims}} =
MyApp.Guardian.refresh(old_token, ttl: {1, :hour})
```
See `Guardian.refresh`
#### `exchange(old_token, from_type, to_type, options)`
Exchanges one token for another of a different type.
Especially useful to trade in a `refresh` token for an `access` one.
Tokens are verified before performing the exchange to ensure that
only valid tokens may be exchanged.
Arguments:
* `old_token` - The existing token you wish to exchange.
* `from_type` - The type the old token must be. Can be given a list of types.
* `to_type` - The new type of token that you want back.
* `options` - The options to pass to the token module and callbacks.
Options:
Options may be used by your token module or callbacks.
* `ttl` - The ttl for the new token
See `Guardian.exchange`
"""
@type options :: Keyword.t()
@type conditional_tuple :: {:ok, any} | {:error, any}
@default_token_module Guardian.Token.Jwt
@doc """
Fetches the subject for a token for the provided resource and claims
The subject should be a short identifier that can be used to identify
the resource
"""
@callback subject_for_token(
resource :: Guardian.Token.resource(),
claims :: Guardian.Token.claims()
) :: {:ok, String.t()} | {:error, atom}
@doc """
Fetches the resource that is represented by claims.
For JWT this would normally be found in the `sub` field
"""
@callback resource_from_claims(claims :: Guardian.Token.claims()) ::
{:ok, Guardian.Token.resource()} | {:error, atom}
@doc """
An optional callback that allows the claims to be modified
while they're being built.
This is useful to hook into the encoding lifecycle.
"""
@callback build_claims(
claims :: Guardian.Token.claims(),
resource :: Guardian.Token.resource(),
opts :: options
) :: {:ok, Guardian.Token.claims()} | {:error, atom}
@doc """
An optional callback invoked after the token has been generated
and signed.
"""
@callback after_encode_and_sign(
resource :: any,
claims :: Guardian.Token.claims(),
token :: Guardian.Token.token(),
options :: options
) :: {:ok, Guardian.Token.token()} | {:error, atom}
@doc """
An optional callback invoked after sign in has been called
By returning an error the sign in will be halted
* Note that if you return an error, a token still may have been generated
"""
@callback after_sign_in(
conn :: Plug.Conn.t(),
resource :: any,
token :: Guardian.Token.token(),
claims :: Guardian.Token.claims(),
options :: options
) :: {:ok, Plug.Conn.t()} | {:error, atom}
@doc """
An optional callback invoked before sign out has happened
"""
@callback before_sign_out(conn :: Plug.Conn.t(), location :: atom | nil, options :: options) ::
{:ok, Plug.Conn.t()} | {:error, atom}
@doc """
An optional callback to add custom verification to claims when
decoding a token
Returning {:ok, claims} will allow the decoding to continue
Returning {:error, reason} will stop the decoding and return the error
"""
@callback verify_claims(claims :: Guardian.Token.claims(), options :: options) ::
{:ok, Guardian.Token.claims()}
| {:error, atom}
@doc """
An optional callback invoked after the claims have been validated
"""
@callback on_verify(
claims :: Guardian.Token.claims(),
token :: Guardian.Token.token(),
options :: options
) :: {:ok, Guardian.Token.claims()} | {:error, any}
@doc """
An optional callback invoked when a token is revoked
"""
@callback on_revoke(
claims :: Guardian.Token.claims(),
token :: Guardian.Token.token(),
options :: options
) :: {:ok, Guardian.Token.claims()} | {:error, any}
@doc """
An optional callback invoked when a token is refreshed
"""
@callback on_refresh(
old_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
new_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
options :: options
) ::
{
:ok,
{Guardian.Token.token(), Guardian.Token.claims()},
{Guardian.Token.token(), Guardian.Token.claims()}
}
| {:error, any}
@doc """
An optional callback invoked when a token is exchanged
"""
@callback on_exchange(
old_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
new_token_and_claims :: {Guardian.Token.token(), Guardian.Token.claims()},
options :: options
) ::
{
:ok,
{Guardian.Token.token(), Guardian.Token.claims()},
{Guardian.Token.token(), Guardian.Token.claims()}
}
| {:error, any}
alias Guardian.Token.Verify
defmodule MalformedReturnValueError do
defexception [:message]
end
defmacro __using__(opts \\ []) do
otp_app = Keyword.get(opts, :otp_app)
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
@behaviour Guardian
if Code.ensure_loaded?(Plug) do
__MODULE__
|> Module.concat(:Plug)
|> Module.create(
quote do
use Guardian.Plug, unquote(__MODULE__)
end,
Macro.Env.location(__ENV__)
)
end
the_otp_app = unquote(otp_app)
the_opts = unquote(opts)
# Provide a way to get at the configuration during compile time
# for other macros that may want to use them
@config fn ->
the_otp_app |> Application.get_env(__MODULE__, []) |> Keyword.merge(the_opts)
end
@config_with_key fn key ->
@config.() |> Keyword.get(key) |> Guardian.Config.resolve_value()
end
@config_with_key_and_default fn key, default ->
@config.() |> Keyword.get(key, default) |> Guardian.Config.resolve_value()
end
@doc """
The default type of token for this module
"""
@spec default_token_type() :: String.t()
def default_token_type, do: "access"
@doc """
Fetches the configuration for this module
"""
@spec config() :: Keyword.t()
def config,
do:
unquote(otp_app)
|> Application.get_env(__MODULE__, [])
|> Keyword.merge(unquote(opts))
@doc """
Returns a resolved value of the configuration found at a key.
See `Guardian.Config.resolve_value/1`.
"""
@spec config(atom | String.t(), any) :: any
def config(key, default \\ nil),
do: config() |> Keyword.get(key, default) |> Guardian.Config.resolve_value()
@doc """
Provides the content of the token but without verification
of either the claims or the signature.
Claims will be present at the `:claims` key.
See `Guardian.peek/2` for more information
"""
@spec peek(String.t()) :: map
def peek(token) do
Guardian.token_module(__MODULE__).peek(__MODULE__, token)
end
@doc """
Encodes the claims.
See `Guardian.encode_and_sign/4` for more information
"""
@spec encode_and_sign(any, Guardian.Token.claims(), Guardian.options()) ::
{:ok, Guardian.Token.token(), Guardian.Token.claims()} | {:error, any}
def encode_and_sign(resource, claims \\ %{}, opts \\ []),
do: Guardian.encode_and_sign(__MODULE__, resource, claims, opts)
@doc """
Decodes and verifies a token using the configuration on the implementation
module.
See `Guardian.decode_and_verify/4`
"""
@spec decode_and_verify(Guardian.Token.token(), Guardian.Token.claims(), Guardian.options()) ::
{:ok, Guardian.Token.claims()} | {:error, any}
def decode_and_verify(token, claims_to_check \\ %{}, opts \\ []),
do: Guardian.decode_and_verify(__MODULE__, token, claims_to_check, opts)
@doc """
Fetch the resource and claims directly from a token
See `Guardian.resource_from_token` for more information
"""
@spec resource_from_token(
token :: Guardian.Token.token(),
claims_to_check :: Guardian.Token.claims() | nil,
opts :: Guardian.options()
) :: {:ok, Guardian.Token.resource(), Guardian.Token.claims()} | {:error, any}
def resource_from_token(token, claims_to_check \\ %{}, opts \\ []),
do: Guardian.resource_from_token(__MODULE__, token, claims_to_check, opts)
@doc """
Revoke a token.
See `Guardian.revoke` for more information
"""
@spec revoke(Guardian.Token.token(), Guardian.options()) ::
{:ok, Guardian.Token.claims()} | {:error, any}
def revoke(token, opts \\ []), do: Guardian.revoke(__MODULE__, token, opts)
@doc """
Refresh a token.
See `Guardian.refresh` for more information
"""
@spec refresh(Guardian.Token.token(), Guardian.options()) ::
{
:ok,
{Guardian.Token.token(), Guardian.Token.claims()},
{Guardian.Token.token(), Guardian.Token.claims()}
}
| {:error, any}
def refresh(old_token, opts \\ []), do: Guardian.refresh(__MODULE__, old_token, opts)
@doc """
Exchanges a token of one type for another.
See `Guardian.exchange` for more information
"""
@spec exchange(
token :: Guardian.Token.token(),
from_type :: String.t() | [String.t(), ...],
to_type :: String.t(),
options :: Guardian.options()
) ::
{
:ok,
{Guardian.Token.token(), Guardian.Token.claims()},
{Guardian.Token.token(), Guardian.Token.claims()}
}
| {:error, any}
def exchange(token, from_type, to_type, opts \\ []),
do: Guardian.exchange(__MODULE__, token, from_type, to_type, opts)
def after_encode_and_sign(_r, _claims, token, _), do: {:ok, token}
def after_sign_in(conn, _r, _t, _c, _o), do: {:ok, conn}
def before_sign_out(conn, _location, _opts), do: {:ok, conn}
def on_verify(claims, _token, _options), do: {:ok, claims}
def on_revoke(claims, _token, _options), do: {:ok, claims}
def on_refresh(old_stuff, new_stuff, _options), do: {:ok, old_stuff, new_stuff}
def on_exchange(old_stuff, new_stuff, _options), do: {:ok, old_stuff, new_stuff}
def build_claims(c, _, _), do: {:ok, c}
def verify_claims(claims, _options), do: {:ok, claims}
defoverridable after_encode_and_sign: 4,
after_sign_in: 5,
before_sign_out: 3,
build_claims: 3,
default_token_type: 0,
on_exchange: 3,
on_revoke: 3,
on_refresh: 3,
on_verify: 3,
peek: 1,
verify_claims: 2
end
end
@doc """
Provides the current system time in seconds
"""
@spec timestamp() :: pos_integer
def timestamp, do: System.system_time(:second)
@doc """
Converts keys in a map or list of maps to strings
"""
@spec stringify_keys(map | list | any) :: map | list | any
def stringify_keys(map) when is_map(map) do
for {k, v} <- map, into: %{}, do: {to_string(k), stringify_keys(v)}
end
def stringify_keys(list) when is_list(list) do
for item <- list, into: [], do: stringify_keys(item)
end
def stringify_keys(value), do: value
@doc """
Returns an inspection of the token (at least claims)
without any verification.
This should not be relied on since there is no verification.
The implementation is provided by the implementation module specified.
See the documentation for your implementation / token module for full details.
"""
@spec peek(module, Guardian.Token.token()) :: %{claims: map}
def peek(mod, token) do
mod.peek(token)
end
@doc """
Creates a signed token for a resource.
The actual encoding depends on the implementation module
which should be referenced for specifics.
### Lifecycle
Once called, a number of callbacks will be invoked on the implementation module:
* `subject_for_token` - gets the subject from the resource
* `build_claims` - allows the implementation module to add or modify claims before the token is created
* `after_encode_and_sign`
### Options
The options will be passed through to the implementation / token modules
and the appropriate callbacks.
* `ttl` - How long to keep the token alive for. If not included the default will be used.
* `token_type` - The type of token to generate if different from the default.
The `ttl` option should take `{integer, unit}` where unit is one of:
* `:second` | `:seconds`
* `:minute` | `:minutes`
* `:hour` | `:hours`
* `:week` | `:weeks`
See the documentation for your implementation / token module for more information on
which options are available for your implementation / token module.
"""
@spec encode_and_sign(module, any, Guardian.Token.claims(), options) ::
{:ok, Guardian.Token.token(), Guardian.Token.claims()} | {:error, any}
def encode_and_sign(mod, resource, claims \\ %{}, opts \\ []) do
claims =
claims
|> Enum.into(%{})
|> Guardian.stringify_keys()
token_mod = Guardian.token_module(mod)
with {:ok, subject} <- returning_tuple({mod, :subject_for_token, [resource, claims]}),
{:ok, claims} <-
returning_tuple({token_mod, :build_claims, [mod, resource, subject, claims, opts]}),
{:ok, claims} <- returning_tuple({mod, :build_claims, [claims, resource, opts]}),
{:ok, token} <- returning_tuple({token_mod, :create_token, [mod, claims, opts]}),
{:ok, _} <-
returning_tuple({mod, :after_encode_and_sign, [resource, claims, token, opts]}) do
{:ok, token, claims}
end
end
@doc """
Decodes a token using the configuration of the implementation module.
This will, using that configuration, delegate to the token module.
Once the token module has decoded the token, your implementation module
has an opportunity to further verify the claims contained in the token
using the `verify_claims` callback.
### Lifecycle
Once called, a number of callbacks will be invoked on the implementation module:
* `verify_claims` - add custom claim verification, returns an error if the claims are not valid
* `on_verify` - called after a successful verification
### Options
The options will be passed through to the implementation / token modules
and the appropriate callbacks.
See the documentation for your implementation / token modules for more information on
which options are available.
"""
@spec decode_and_verify(module, Guardian.Token.token(), Guardian.Token.claims(), options) ::
{:ok, Guardian.Token.claims()} | {:error, any}
def decode_and_verify(mod, token, claims_to_check \\ %{}, opts \\ []) do
claims_to_check = claims_to_check |> Enum.into(%{}) |> Guardian.stringify_keys()
token_mod = Guardian.token_module(mod)
with {:ok, claims} <- returning_tuple({token_mod, :decode_token, [mod, token, opts]}),
{:ok, claims} <- Verify.verify_literal_claims(claims, claims_to_check, opts),
{:ok, claims} <- returning_tuple({token_mod, :verify_claims, [mod, claims, opts]}),
{:ok, claims} <- returning_tuple({mod, :verify_claims, [claims, opts]}),
{:ok, claims} <- returning_tuple({mod, :on_verify, [claims, token, opts]}) do
{:ok, claims}
end
rescue
e -> {:error, e}
end
@doc """
Fetch the resource and claims directly from a token.
This is a convenience function that first decodes the token using `Guardian.decode_and_verify/4` and then loads the resource.
"""
@spec resource_from_token(
mod :: module,
token :: Guardian.Token.token(),
claims_to_check :: Guardian.Token.claims() | nil,
opts :: options
) :: {:ok, Guardian.Token.resource(), Guardian.Token.claims()} | {:error, any}
def resource_from_token(mod, token, claims_to_check \\ %{}, opts \\ []) do
with {:ok, claims} <- Guardian.decode_and_verify(mod, token, claims_to_check, opts),
{:ok, resource} <- returning_tuple({mod, :resource_from_claims, [claims]}) do
{:ok, resource, claims}
end
end
@doc """
Revoke a token.
Note: This is entirely dependent on the token module and callbacks.
### Lifecycle
* `<TokenModule>.revoke`
* `<ImplModule>.on_revoke`
### Options
The options are passed through to the token module and callback
so check the documentation for your token module.
"""
@spec revoke(module, Guardian.Token.token(), options) ::
{:ok, Guardian.Token.claims()} | {:error, any}
def revoke(mod, token, opts \\ []) do
token_mod = Guardian.token_module(mod)
with %{claims: claims} <- mod.peek(token),
{:ok, claims} <- returning_tuple({token_mod, :revoke, [mod, claims, token, opts]}),
{:ok, claims} <- returning_tuple({mod, :on_revoke, [claims, token, opts]}) do
{:ok, claims}
else
nil -> {:error, :not_found}
{:error, _} = err -> err
end
end
@doc """
Refreshes a token keeping all main claims intact.
### Options
* `ttl` - How long to keep the token alive for. If not included the default will be used.
The `ttl` option should take `{integer, unit}` where unit is one of:
* `:second` | `:seconds`
* `:minute` | `:minutes`
* `:hour` | `:hours`
* `:day` | `:days`
* `:week` | `:weeks`
See documentation for your token module for other options.
"""
@spec refresh(module, Guardian.Token.token(), options) ::
{
:ok,
{Guardian.Token.token(), Guardian.Token.claims()},
{Guardian.Token.token(), Guardian.Token.claims()}
}
| {:error, any}
def refresh(mod, old_token, opts) do
with token_mod <- Guardian.token_module(mod),
{:ok, _claims} <- apply(mod, :decode_and_verify, [old_token, %{}, opts]),
{:ok, old_stuff, new_stuff} <- apply(token_mod, :refresh, [mod, old_token, opts]) do
apply(mod, :on_refresh, [old_stuff, new_stuff, opts])
else
{:error, _} = err -> err
err -> {:error, err}
end
end
@doc """
Exchanges one token for another with different token types.
The token is first decoded and verified to ensure that there is no escalation
Of privileges.
Tokens must have their type included in the `from_type` argument.
### Lifecycle
* `<TokenModule>.exchange` - exchange the old token for the new one
* `<ImplModule>.on_exchange` - will be invoked after the exchange happens
### Options
All options are passed through all calls to the token module and
appropriate callbacks.
"""
@spec exchange(
module,
Guardian.Token.token(),
String.t() | [String.t(), ...],
String.t(),
options
) ::
{
:ok,
{Guardian.Token.token(), Guardian.Token.claims()},
{Guardian.Token.token(), Guardian.Token.claims()}
}
| {:error, any}
def exchange(mod, old_token, from_type, to_type, opts) do
with token_mod <- Guardian.token_module(mod),
{:ok, claims} <- apply(mod, :decode_and_verify, [old_token, %{}, opts]),
:ok <- validate_exchange_type(claims, from_type),
{:ok, old_stuff, new_stuff} <-
apply(token_mod, :exchange, [mod, old_token, from_type, to_type, opts]) do
apply(mod, :on_exchange, [old_stuff, new_stuff, opts])
else
{:error, _} = err -> err
err -> {:error, err}
end
end
@doc false
def returning_tuple({mod, func, args}) do
result = apply(mod, func, args)
case result do
{:ok, _} ->
result
{:error, _} ->
result
resp ->
raise MalformedReturnValueError,
message:
"Expected `{:ok, result}` or `{:error, reason}` from #{mod}##{func}, got: #{
inspect(resp)
}"
end
end
@doc false
def token_module(mod) do
apply(mod, :config, [:token_module, @default_token_module])
end
defp validate_exchange_type(claims, from_type) when is_binary(from_type),
do: validate_exchange_type(claims, [from_type])
defp validate_exchange_type(claims, from_type) do
if Enum.member?(from_type, claims["typ"]), do: :ok, else: {:error, :invalid_token_type}
end
end