./ct_report/coverage/ejabberd_auth_jwt.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_auth_jwt.erl
3 %%% Author : Astro <astro@spaceboyz.net>
4 %%% Purpose : Authentification with JSON Web Tokens
5 %%% Created : 02 Aug 2016 by Stephan Maka <stephan@spaceboyz.net>
6 %%%
7 %%%
8 %%% MongooseIM, Copyright (C) 2016 CostaDigital
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(ejabberd_auth_jwt).
27 -author('astro@spaceboyz.net').
28
29 %% External exports
30 -behaviour(mongoose_gen_auth).
31
32 -export([start/1,
33 stop/1,
34 config_spec/0,
35 authorize/1,
36 check_password/4,
37 check_password/6,
38 does_user_exist/3,
39 supports_sasl_module/2,
40 supported_features/0
41 ]).
42
43 %% Config spec callbacks
44 -export([process_jwt_secret/1]).
45
46 -include("mongoose.hrl").
47 -include("mongoose_config_spec.hrl").
48
49 %%%----------------------------------------------------------------------
50 %%% API
51 %%%----------------------------------------------------------------------
52
53 -spec start(HostType :: mongooseim:host_type()) -> ok.
54 start(HostType) ->
55
:-(
JWTSecret = get_jwt_secret(HostType),
56
:-(
persistent_term:put({?MODULE, HostType, jwt_secret}, JWTSecret),
57
:-(
ok.
58
59 -spec stop(HostType :: mongooseim:host_type()) -> ok.
60 stop(_HostType) ->
61
:-(
persistent_term:erase(jwt_secret),
62
:-(
ok.
63
64 -spec config_spec() -> mongoose_config_spec:config_section().
65 config_spec() ->
66 106 #section{
67 items = #{<<"secret">> => jwt_secret_config_spec(),
68 <<"algorithm">> => #option{type = binary,
69 validate = {enum, algorithms()}},
70 <<"username_key">> => #option{type = atom,
71 validate = non_empty}
72 },
73 required = all
74 }.
75
76 jwt_secret_config_spec() ->
77 106 #section{
78 items = #{<<"file">> => #option{type = string,
79 validate = filename},
80 <<"env">> => #option{type = string,
81 validate = non_empty},
82 <<"value">> => #option{type = binary}},
83 format_items = list,
84 process = fun ?MODULE:process_jwt_secret/1
85 }.
86
87
:-(
process_jwt_secret([V]) -> V.
88
89 -spec supports_sasl_module(binary(), cyrsasl:sasl_module()) -> boolean().
90
:-(
supports_sasl_module(_, Module) -> Module =:= cyrsasl_plain.
91
92 -spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()}
93 | {error, any()}.
94 authorize(Creds) ->
95
:-(
ejabberd_auth:authorize_with_check_password(?MODULE, Creds).
96
97 -spec check_password(HostType :: mongooseim:host_type(),
98 LUser :: jid:luser(),
99 LServer :: jid:lserver(),
100 Password :: binary()) -> boolean().
101 check_password(HostType, LUser, LServer, Password) ->
102
:-(
Key = case persistent_term:get({?MODULE, HostType, jwt_secret}) of
103
:-(
Key1 when is_binary(Key1) -> Key1;
104
:-(
{env, Var} -> list_to_binary(os:getenv(Var))
105 end,
106
:-(
BinAlg = mongoose_config:get_opt([{auth, HostType}, jwt, algorithm]),
107
:-(
Alg = binary_to_atom(jid:str_tolower(BinAlg), utf8),
108
:-(
case jwerl:verify(Password, Alg, Key) of
109 {ok, TokenData} ->
110
:-(
UserKey = mongoose_config:get_opt([{auth,HostType}, jwt, username_key]),
111
:-(
case maps:find(UserKey, TokenData) of
112 {ok, LUser} ->
113 %% Login username matches $token_user_key in TokenData
114
:-(
?LOG_INFO(#{what => jwt_success_auth,
115 text => <<"Successfully authenticated with JWT">>,
116 user => LUser, server => LServer,
117
:-(
token => TokenData}),
118
:-(
true;
119 {ok, ExpectedUser} ->
120
:-(
?LOG_WARNING(#{what => wrong_jwt_user,
121 text => <<"JWT contains wrond user">>,
122 expected_user => ExpectedUser,
123
:-(
user => LUser, server => LServer}),
124
:-(
false;
125 error ->
126
:-(
?LOG_WARNING(#{what => missing_jwt_key,
127 text => <<"Missing key {user_key} in JWT data">>,
128 user_key => UserKey, token => TokenData,
129
:-(
user => LUser, server => LServer}),
130
:-(
false
131 end;
132 {error, Reason} ->
133
:-(
?LOG_WARNING(#{what => jwt_verification_failed,
134 text => <<"Cannot verify JWT for user">>,
135 reason => Reason,
136
:-(
user => LUser, server => LServer}),
137
:-(
false
138 end.
139
140
141 -spec check_password(HostType :: mongooseim:host_type(),
142 LUser :: jid:luser(),
143 LServer :: jid:lserver(),
144 Password :: binary(),
145 Digest :: binary(),
146 DigestGen :: fun()) -> boolean().
147 check_password(HostType, LUser, LServer, Password, _Digest, _DigestGen) ->
148
:-(
check_password(HostType, LUser, LServer, Password).
149
150 -spec does_user_exist(HostType :: mongooseim:host_type(),
151 LUser :: jid:luser(),
152 LServer :: jid:lserver()) -> boolean() | {error, atom()}.
153 does_user_exist(_HostType, _LUser, _LServer) ->
154
:-(
true.
155
156 -spec supported_features() -> [atom()].
157
:-(
supported_features() -> [dynamic_domains].
158
159 %%%----------------------------------------------------------------------
160 %%% Internal helpers
161 %%%----------------------------------------------------------------------
162
163 % A direct path to a file is read only once during startup,
164 % a path in environment variable is read on every auth request.
165 -spec get_jwt_secret(mongooseim:host_type()) -> binary() | {env, string()}.
166 get_jwt_secret(HostType) ->
167
:-(
case mongoose_config:get_opt([{auth, HostType}, jwt, secret]) of
168 {value, JWTSecret} ->
169
:-(
JWTSecret;
170 {env, Env} ->
171
:-(
{env, Env};
172 {file, Path} ->
173
:-(
{ok, JWTSecret} = file:read_file(Path),
174
:-(
JWTSecret
175 end.
176
177 algorithms() ->
178 106 [<<"HS256">>, <<"RS256">>, <<"ES256">>,
179 <<"HS386">>, <<"RS386">>, <<"ES386">>,
180 <<"HS512">>, <<"RS512">>, <<"ES512">>].
Line Hits Source