Added SSL, CORS support for the API.
This commit is contained in:
parent
1e05258381
commit
100ca8fd74
146
src/ts_api.erl
146
src/ts_api.erl
@ -40,8 +40,8 @@ dispatch_request(YArg, Session, [H|T]) ->
|
||||
{_, "login"} -> do_login(YArg);
|
||||
{_, "logout"} -> do_logout(YArg);
|
||||
|
||||
{"not_logged_in", _} -> make_json_401(YArg);
|
||||
{"session_expired", _} -> make_json_401(YArg, [{error, "session expired"}]);
|
||||
{not_logged_in, _} -> make_json_401(YArg);
|
||||
{session_expired, _} -> make_json_401(YArg, [{error, "session expired"}]);
|
||||
|
||||
{_S, "app"} -> dispatch_app(YArg, Session, T);
|
||||
{_S, "users"} -> dispatch_user(YArg, Session, T);
|
||||
@ -57,6 +57,8 @@ dispatch_app(YArg, Session, Params) ->
|
||||
|
||||
case {HTTPMethod, Params} of
|
||||
|
||||
{'OPTIONS', ["user_summary", _]} -> make_CORS_options(YArg, "GET");
|
||||
|
||||
{'GET', ["user_summary", UsernameStr]} ->
|
||||
case {Session#ts_api_session.username,
|
||||
UsernameStr} of
|
||||
@ -83,8 +85,9 @@ dispatch_user(YArg, Session, [Username]) ->
|
||||
% compare to the logged-in user
|
||||
case {HTTPMethod, Session#ts_api_session.username} of
|
||||
|
||||
{'GET', Username} -> get_user(YArg, Username);
|
||||
{'PUT', Username} -> put_user(YArg, Username);
|
||||
{'OPTIONS', Username} -> make_CORS_options(YArg, "GET, PUT");
|
||||
{'GET', Username} -> get_user(YArg, Username);
|
||||
{'PUT', Username} -> put_user(YArg, Username);
|
||||
|
||||
{_BadMethod, Username} ->
|
||||
make_json_405(YArg, [{see_docs, "/ts_api_doc/users.html"}]);
|
||||
@ -109,14 +112,16 @@ dispatch_timeline(YArg, [Username]) ->
|
||||
HTTPMethod = (YArg#arg.req)#http_request.method,
|
||||
|
||||
case HTTPMethod of
|
||||
'GET' -> list_timelines(YArg, Username);
|
||||
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines.html"}])
|
||||
'OPTIONS' -> make_CORS_options(YArg, "GET");
|
||||
'GET' -> list_timelines(YArg, Username);
|
||||
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines.html"}])
|
||||
end;
|
||||
|
||||
dispatch_timeline(YArg, [Username, TimelineId]) ->
|
||||
HTTPMethod = (YArg#arg.req)#http_request.method,
|
||||
|
||||
case HTTPMethod of
|
||||
'OPTIONS'-> make_CORS_options(YArg, "GET, PUT, DELETE");
|
||||
'GET' -> get_timeline(YArg, Username, TimelineId);
|
||||
'PUT' -> put_timeline(YArg, Username, TimelineId);
|
||||
'DELETE' -> delete_timeline(YArg, Username, TimelineId);
|
||||
@ -142,9 +147,10 @@ dispatch_entry(YArg, [Username, TimelineId]) ->
|
||||
HTTPMethod = (YArg#arg.req)#http_request.method,
|
||||
|
||||
case HTTPMethod of
|
||||
'GET' -> list_entries(YArg, Username, TimelineId);
|
||||
'POST' -> post_entry(YArg, Username, TimelineId);
|
||||
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
|
||||
'OPTIONS' -> make_CORS_options(YArg, "GET, POST");
|
||||
'GET' -> list_entries(YArg, Username, TimelineId);
|
||||
'POST' -> post_entry(YArg, Username, TimelineId);
|
||||
_Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/entries.html"}])
|
||||
end;
|
||||
|
||||
dispatch_entry(YArg, [Username, TimelineId, UrlEntryId]) ->
|
||||
@ -152,6 +158,7 @@ dispatch_entry(YArg, [Username, TimelineId, UrlEntryId]) ->
|
||||
HTTPMethod = (YArg#arg.req)#http_request.method,
|
||||
|
||||
case HTTPMethod of
|
||||
'OPTIONS'-> make_CORS_options(YArg, "GET, PUT, DELETE");
|
||||
'GET' -> get_entry(YArg, Username, TimelineId, EntryId);
|
||||
'PUT' -> put_entry(YArg, Username, TimelineId, EntryId);
|
||||
'DELETE' -> delete_entry(YArg, Username, TimelineId, EntryId);
|
||||
@ -182,11 +189,13 @@ do_login(YArg) ->
|
||||
true ->
|
||||
{CookieVal, _Session} = ts_api_session:new(Username),
|
||||
|
||||
[{content, "application/json",
|
||||
json:encode({struct, [{status, "ok"}]})},
|
||||
{header, {set_cookie, io_lib:format(
|
||||
"ts_api_session=~s; Path=/ts_api; httponly",
|
||||
[CookieVal])}}];
|
||||
[{header, {set_cookie, io_lib:format(
|
||||
"ts_api_session=~s; Path=/ts_api",
|
||||
[CookieVal])}},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]},
|
||||
{content, "application/json",
|
||||
json:encode({struct, [{status, "ok"}]})}];
|
||||
|
||||
% they are not good
|
||||
false -> make_json_401(YArg, [{error,
|
||||
@ -200,7 +209,9 @@ do_logout(YArg) ->
|
||||
Cookie = (YArg#arg.headers)#headers.cookie,
|
||||
CookieVal = yaws_api:find_cookie_val("ts_api_session", Cookie),
|
||||
ts_api_session:logout(CookieVal),
|
||||
{status, 200}.
|
||||
[{status, 200},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
get_user_summary(YArg, Username) ->
|
||||
% find user record
|
||||
@ -229,14 +240,11 @@ get_user_summary(YArg, Username) ->
|
||||
end,
|
||||
Timelines)},
|
||||
|
||||
% convert to JSON
|
||||
JSONResp = json:encode({struct,
|
||||
% write response out
|
||||
make_json_200(YArg, {struct,
|
||||
[{user, EJSONUser},
|
||||
{timelines, EJSONTimelines}
|
||||
]}),
|
||||
|
||||
% write response out
|
||||
{content, "application/json", JSONResp}
|
||||
]})
|
||||
end.
|
||||
|
||||
get_user(YArg, Username) ->
|
||||
@ -245,7 +253,7 @@ get_user(YArg, Username) ->
|
||||
% no such user, barf
|
||||
no_record -> make_json_404(YArg);
|
||||
% found, return a 200 with the record
|
||||
User -> make_json_200(YArg, User)
|
||||
User -> make_json_200_record(YArg, User)
|
||||
end.
|
||||
|
||||
put_user(YArg, Username) ->
|
||||
@ -265,7 +273,7 @@ put_user(YArg, Username) ->
|
||||
{ok, UpdatedRec} = ts_user:update(UR, ExtData),
|
||||
|
||||
% return a 200
|
||||
make_json_200(YArg, UpdatedRec).
|
||||
make_json_200_record(YArg, UpdatedRec).
|
||||
|
||||
list_timelines(YArg, Username) ->
|
||||
% pull out the POST data
|
||||
@ -295,11 +303,8 @@ list_timelines(YArg, Username) ->
|
||||
end,
|
||||
Timelines)},
|
||||
|
||||
% convert to JSON and create resposne
|
||||
JSONResponse = json:encode(EJSONTimelines),
|
||||
|
||||
% return response
|
||||
{content, "application/json", JSONResponse}.
|
||||
make_json_200(YArg, EJSONTimelines).
|
||||
|
||||
get_timeline(YArg, Username, TimelineId) ->
|
||||
% look for timeline
|
||||
@ -307,7 +312,7 @@ get_timeline(YArg, Username, TimelineId) ->
|
||||
% no such timeline, return 404
|
||||
no_record -> make_json_404(YArg, [{error, "no such timeline"}]);
|
||||
% return the timeline data
|
||||
Timeline -> make_json_200(YArg, Timeline)
|
||||
Timeline -> make_json_200_record(YArg, Timeline)
|
||||
end.
|
||||
|
||||
put_timeline(YArg, Username, TimelineId) ->
|
||||
@ -329,7 +334,7 @@ put_timeline(YArg, Username, TimelineId) ->
|
||||
ts_timeline:write(TR, ExtData),
|
||||
|
||||
% return a 200
|
||||
make_json_200(YArg, TR).
|
||||
make_json_200_record(YArg, TR).
|
||||
|
||||
delete_timeline(_YArg, _Username, _TimelineId) -> {status, 405}.
|
||||
|
||||
@ -380,9 +385,7 @@ list_entries(YArg, Username, TimelineId) ->
|
||||
end,
|
||||
Entries)},
|
||||
|
||||
JSONResponse = json:encode(EJSONEntries),
|
||||
|
||||
{content, "application/json", JSONResponse};
|
||||
make_json_200(YArg, EJSONEntries);
|
||||
|
||||
% listing by table position
|
||||
_Other ->
|
||||
@ -416,9 +419,7 @@ list_entries(YArg, Username, TimelineId) ->
|
||||
end,
|
||||
Entries)},
|
||||
|
||||
JSONResponse = json:encode(EJSONEntries),
|
||||
|
||||
{content, "application/json", JSONResponse}
|
||||
make_json_200(YArg, EJSONEntries)
|
||||
end.
|
||||
|
||||
get_entry(YArg, Username, TimelineId, EntryId) ->
|
||||
@ -426,7 +427,7 @@ get_entry(YArg, Username, TimelineId, EntryId) ->
|
||||
% no such entry
|
||||
no_record -> make_json_404(YArg, [{error, "no such entry"}]);
|
||||
% return the entry data
|
||||
Entry -> make_json_200(YArg, Entry)
|
||||
Entry -> make_json_200_record(YArg, Entry)
|
||||
end.
|
||||
|
||||
post_entry(YArg, Username, TimelineId) ->
|
||||
@ -446,7 +447,7 @@ post_entry(YArg, Username, TimelineId) ->
|
||||
% record created
|
||||
{ok, CreatedRecord} ->
|
||||
|
||||
[{status, 201}, make_json_200(YArg, CreatedRecord)];
|
||||
[{status, 201}, make_json_200_record(YArg, CreatedRecord)];
|
||||
|
||||
OtherError ->
|
||||
error_logger:error_report("Could not create entry: ~p", [OtherError]),
|
||||
@ -467,7 +468,7 @@ put_entry(YArg, Username, TimelineId, EntryId) ->
|
||||
end,
|
||||
|
||||
ts_entry:write(ER, ExtData),
|
||||
make_json_200(YArg, ER).
|
||||
make_json_200_record(YArg, ER).
|
||||
|
||||
delete_entry(YArg, Username, TimelineId, EntryId) ->
|
||||
|
||||
@ -499,31 +500,65 @@ parse_json_body(YArg) ->
|
||||
throw(make_json_400(YArg))
|
||||
end.
|
||||
|
||||
get_origin_header(YArg) ->
|
||||
Headers = (YArg#arg.headers)#headers.other,
|
||||
case lists:keyfind("Origin", 3, Headers) of
|
||||
false -> "*";
|
||||
{http_header, 0, "Origin", _, Origin} -> Origin;
|
||||
_ -> make_json_500(YArg, "Unrecognized Origin header.")
|
||||
end.
|
||||
|
||||
make_CORS_options(YArg, AllowedMethods) ->
|
||||
[{status, 200},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Methods: ", AllowedMethods]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
make_CORS_options(_YArg, AllowedOrigins, AllowedMethods) ->
|
||||
[{status, 200},
|
||||
{header, ["Access-Control-Allow-Origin: ", AllowedOrigins]},
|
||||
{header, ["Access-Control-Allow-Methods: ", AllowedMethods]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
%% Create a JSON 200 response.
|
||||
make_json_200(_YArg, Record) ->
|
||||
make_json_200(YArg, EJSONResponse) ->
|
||||
JSONResponse = json:encode(EJSONResponse),
|
||||
[{content, "application/json", JSONResponse},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
|
||||
make_json_200_record(YArg, Record) ->
|
||||
RecordExtData = ts_ext_data:get_properties(Record),
|
||||
EJSON = ts_json:record_to_ejson(Record, RecordExtData),
|
||||
JSONResponse = json:encode(EJSON),
|
||||
{content, "application/json", JSONResponse}.
|
||||
[{content, "application/json", JSONResponse},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
make_json_400(YArg) -> make_json_400(YArg, []).
|
||||
make_json_400(_YArg, Fields) ->
|
||||
make_json_400(YArg, Fields) ->
|
||||
F1 = case lists:keyfind(status, 1, Fields) of
|
||||
false -> Fields ++ [{status, "bad request"}];
|
||||
_Else -> Fields
|
||||
end,
|
||||
|
||||
[{status, 400}, {content, "application/json", json:encode({struct, F1})}].
|
||||
[{status, 400}, {content, "application/json", json:encode({struct, F1})},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
make_json_401(YArg) -> make_json_401(YArg, []).
|
||||
make_json_401(_YArg, Fields) ->
|
||||
make_json_401(YArg, Fields) ->
|
||||
% add default status if not provided
|
||||
F1 = case lists:keyfind(status, 1, Fields) of
|
||||
false -> Fields ++ [{status, "unauthorized"}];
|
||||
_Else -> Fields
|
||||
end,
|
||||
|
||||
[{status, 401}, {content, "application/json", json:encode({struct, F1})}].
|
||||
[{status, 401},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]},
|
||||
{content, "application/json", json:encode({struct, F1})}].
|
||||
|
||||
%% Create a JSON 404 response.
|
||||
make_json_404(YArg) -> make_json_404(YArg, []).
|
||||
@ -537,10 +572,12 @@ make_json_404(YArg, Fields) ->
|
||||
% add the path they requested
|
||||
F2 = F1 ++ [{path, element(2, (YArg#arg.req)#http_request.path)}],
|
||||
|
||||
[{status, 404}, {content, "application/json", json:encode({struct, F2})}].
|
||||
[{status, 404}, {content, "application/json", json:encode({struct, F2})},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
make_json_405(YArg) -> make_json_405(YArg, []).
|
||||
make_json_405(_YArg, Fields) ->
|
||||
make_json_405(YArg, Fields) ->
|
||||
% add default status if not provided
|
||||
F1 = case lists:keyfind(status, 1, Fields) of
|
||||
false -> Fields ++ [{status, "method not allowed"}];
|
||||
@ -550,15 +587,22 @@ make_json_405(_YArg, Fields) ->
|
||||
% add the path they requested
|
||||
% F2 = F1 ++ [{path, io_lib:format("~p", [(YArg#arg.req)#http_request.path])}],
|
||||
|
||||
[{status, 405}, {content, "application/json", json:encode({struct, F1})}].
|
||||
[{status, 405}, {content, "application/json", json:encode({struct, F1})},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
make_json_500(_YArg, Error) ->
|
||||
make_json_500(YArg, Error) ->
|
||||
io:format("Error: ~n~p", [Error]),
|
||||
EJSON = {struct, [
|
||||
{status, "internal server error"},
|
||||
{error, lists:flatten(io_lib:format("~p", [Error]))}]},
|
||||
[{status, 500}, {content, "application/json", json:encode(EJSON)}].
|
||||
[{status, 500}, {content, "application/json", json:encode(EJSON)},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
||||
make_json_500(_YArg) ->
|
||||
make_json_500(YArg) ->
|
||||
EJSON = {struct, [
|
||||
{status, "internal server error"}]},
|
||||
[{status, 500}, {content, "application/json", json:encode(EJSON)}].
|
||||
[{status, 500}, {content, "application/json", json:encode(EJSON)},
|
||||
{header, ["Access-Control-Allow-Origin: ", get_origin_header(YArg)]},
|
||||
{header, ["Access-Control-Allow-Credentials: ", "true"]}].
|
||||
|
@ -9,3 +9,17 @@ include_dir = /usr/local/var/yaws/jdb-labs.com/timestamper/include
|
||||
docroot = /usr/local/var/yaws/jdb-labs.com/timestamper/www
|
||||
appmods = ts_api
|
||||
</server>
|
||||
|
||||
<server timestamper.jdb-labs.com>
|
||||
port = 443
|
||||
listen = 0.0.0.0
|
||||
docroot = /usr/local/var/yaws/jdb-labs.com/timestamper/www
|
||||
appmods = ts_api
|
||||
dir_listings = false
|
||||
<ssl>
|
||||
keyfile = /usr/local/var/yaws/keys/jdb-labs.com.key.pem
|
||||
certfile = /usr/local/var/yaws/keys/jdb-labs.com.cert.pem
|
||||
depth = 0
|
||||
</ssl>
|
||||
</server>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user