diff --git a/db/test/DECISION_TAB.LOG b/db/test/DECISION_TAB.LOG index 65225c9..318ffdb 100644 Binary files a/db/test/DECISION_TAB.LOG and b/db/test/DECISION_TAB.LOG differ diff --git a/db/test/LATEST.LOG b/db/test/LATEST.LOG index a975577..450f6f5 100644 Binary files a/db/test/LATEST.LOG and b/db/test/LATEST.LOG differ diff --git a/db/test/ts_user.DCD b/db/test/ts_user.DCD index 995b8be..1e2c376 100644 Binary files a/db/test/ts_user.DCD and b/db/test/ts_user.DCD differ diff --git a/login.post b/login.post new file mode 100644 index 0000000..a603de9 --- /dev/null +++ b/login.post @@ -0,0 +1,5 @@ +POST /ts_api/login HTTP/1.0 +Content-Type: application/json +Content-Length: 45 + +{"username":"jdbernard", "password":"Y0uthc"} diff --git a/src/timestamper.erl b/src/timestamper.erl new file mode 100644 index 0000000..aaa035e --- /dev/null +++ b/src/timestamper.erl @@ -0,0 +1,16 @@ +-module(timestamper). +-export([start/0, create_tables/1]). + +start() -> + ok = application:load(mnesia), + ok = application:set_env(mnesia, dir, "/home/jdbernard/projects/timestamper/web-app/db/test"), + ok = mnesia:start(), + ok. + +create_tables(Nodes) -> + TableOpts = [{disc_copies, Nodes}], + {atomic, ok} = id_counter:create_table(TableOpts), + {atomic, ok} = ts_user:create_table(TableOpts), + {atomic, ok} = ts_timeline:create_table(TableOpts), + {atmoic, ok} = ts_entry:create_table(TableOpts), + ok. diff --git a/src/ts_api.erl b/src/ts_api.erl index 2170ee4..391574d 100644 --- a/src/ts_api.erl +++ b/src/ts_api.erl @@ -5,6 +5,9 @@ -include("yaws_api.hrl"). out(YArg) -> + % retreive the session data + Session = ts_api_session:get_session(YArg), + %get the app mod data PathString = YArg#arg.appmoddata, @@ -14,54 +17,74 @@ out(YArg) -> _Any -> re:split(PathString, "/", [{return, list}]) end, - % process request - dispatch_request(YArg, PathElements). + % process the request + case catch dispatch_request(YArg, Session, PathElements) of + {'EXIT', Err} -> + % TODO: log error internally + io:format("~p", [Err]), + make_json_500(YArg); + Other -> Other + end. % ================================== % % ======== DISPATCH METHODS ======== % % ================================== % %% Entry point to the TimeStamper API dispatch system -dispatch_request(YArg, []) -> make_json_404(YArg, [{see_docs, "/ts_api_doc"}]); +dispatch_request(YArg, _Session, []) -> make_json_404(YArg, [{see_docs, "/ts_api_doc"}]); -dispatch_request(YArg, [H|T]) -> +dispatch_request(YArg, Session, [H|T]) -> Param = path_element_to_atom(H), - case Param of - users -> dispatch_user(YArg, T); - timelines -> dispatch_timeline(YArg, T); - entries -> dispatch_entry(YArg, T); - _Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/"}]) + case {Session, Param} of + {_, login} -> do_login(YArg); + {_, logout} -> do_logout(YArg); + + {not_logged_in, _} -> make_json_401(YArg); + {session_expired, _} -> make_json_401(YArg, [{status, "session expired"}]); + + {_S, users} -> dispatch_user(YArg, Session, T); + {_S, timelines} -> dispatch_timeline(YArg, Session, T); + {_S, entries} -> dispatch_entry(YArg, Session, T); + {_S, _Other} -> make_json_404(YArg, [{see_docs, "/ts_api_doc/"}]) end. -dispatch_user(YArg, []) -> - HTTPMethod = (YArg#arg.req)#http_request.method, +dispatch_user(YArg, _Session, []) -> + make_json_404(YArg, [{see_docs, "/ts_api_doc/"}]); - case HTTPMethod of - 'PUT' -> put_user(YArg); - _Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/"}]) - end; - -dispatch_user(YArg, [H|T]) -> +dispatch_user(YArg, Session, [H]) -> Username = path_element_to_atom(H), HTTPMethod = (YArg#arg.req)#http_request.method, - case {HTTPMethod, T} of - {'GET', []} -> get_user(YArg, Username); - {'POST', []} -> post_user(YArg, Username); - {'DELETE', []} -> delete_user(YArg, Username); - {_Other, []} -> make_json_405(YArg, [{see_docs, "/ts_api_doc/users"}]); - _Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/users"}]) + % compare to the logged-in user + case {HTTPMethod, Session#ts_api_session.username} of + + {'GET', Username} -> get_user(YArg, Username); + + {_BadMethod, Username} -> + make_json_405(YArg, [{see_docs, "/ts_api_doc/users"}]); + + _Other -> make_json_401(YArg, [{see_docs, "/ts_api_doc/users"}]) end. -% just username, list timelines or create a new one +dispatch_timeline(YArg, _Session, []) -> + make_json_404(YArg, [{see_docs, "/ts_api_doc/timelines"}]); + +dispatch_timeline(YArg, Session, [UrlUsername|_T] = PathElements) -> + Username = path_element_to_atom(UrlUsername), + + case Session#ts_api_session.username of + Username -> dispatch_timeline(YArg, PathElements); + _Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/users"}]) + end. + +% just username, list timelines dispatch_timeline(YArg, [UrlUsername]) -> Username = path_element_to_atom(UrlUsername), HTTPMethod = (YArg#arg.req)#http_request.method, case HTTPMethod of 'GET' -> list_timelines(YArg, Username); - 'PUT' -> put_timeline(YArg, Username); _Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines"}]) end; @@ -71,15 +94,27 @@ dispatch_timeline(YArg, [UrlUsername, UrlTimelineId]) -> HTTPMethod = (YArg#arg.req)#http_request.method, case HTTPMethod of - 'GET' -> get_timeline(YArg, Username, TimelineId); - 'POST' -> post_timeline(YArg, Username, TimelineId); - 'DELETE' -> delete_timeline(YArg, Username, TimelineId); + 'GET' -> get_timeline(YArg, Username, TimelineId); + 'POST' -> post_timeline(YArg, Username, TimelineId); + 'PUT' -> put_timeline(YArg, Username, TimelineId); + 'DELETE' -> delete_timeline(YArg, Username, TimelineId); _Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines"}]) end; dispatch_timeline(YArg, _Other) -> make_json_404(YArg, [{see_docs, "/ts_api_doc/timelines"}]). +dispatch_entry(YArg, _Session, []) -> + make_json_404(YArg, [{see_docs, "/ts_aip_doc/entries"}]); + +dispatch_entry(YArg, Session, [UrlUsername|_T] = PathElements) -> + Username = path_element_to_atom(UrlUsername), + + case Session#ts_api_session.username of + Username -> dispatch_entry(YArg, PathElements); + _Other -> make_json_404(YArg, [{see_docs, "/ts_api_doc/entries"}]) + end. + dispatch_entry(YArg, [UrlUsername, UrlTimelineId]) -> Username = path_element_to_atom(UrlUsername), TimelineId = path_element_to_atom(UrlTimelineId), @@ -111,53 +146,49 @@ dispatch_entry(YArg, _Other) -> % ======== IMPLEMENTATION ====== % % ============================== % +do_login(YArg) -> + EJSON = parse_json_body(YArg), + + {struct, Fields} = EJSON, + + case {lists:keyfind(username, 1, Fields), + lists:keyfind(password, 1, Fields)} of + + % username an password found + {{username, UnameField}, {password, Password}} -> + Username = list_to_atom(UnameField), + + % check the uname, password + case ts_user:check_credentials(Username, Password) of + % they are good + 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])}}]; + + % they are not good + false -> make_json_401(YArg, [{status, + "bad username/password combination"}]) + end; + + _Other -> make_json_400(YArg, [{see_docs, "/ts_api_doc/login"}]) + end. + +do_logout(YArg) -> + Cookie = (YArg#arg.headers)#headers.cookie, + CookieVal = yaws_api:find_cookie_val("ts_api_session", Cookie), + ts_api_session:logout(CookieVal). + get_user(YArg, Username) -> case ts_user:lookup(Username) of no_record -> make_json_404(YArg); User -> make_json_200(YArg, User) end. -put_user(YArg) -> - % parse the request body - {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), - - % parse into a user record - NewRecord = ts_json:ejson_to_record(#ts_user{}, EJSON), - - case ts_user:new(NewRecord) of - % record created - ok -> [{status, 201}, make_json_200(YArg, NewRecord)]; - - % will not create, record exists - {error, {record_exists, ExistingRecord}} -> - JSONResponse = json:encode({struct, [ - {status, "username taken"}, - {see_docs, "/ts_api_doc/user#PUT"} - ]}), - - [{status, 409}, {content, "application/json", JSONResponse}]; - - _Error -> make_json_500(YArg) - end. - -post_user(YArg, Username) -> - % parse the POST data - {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), - - % create the user record - NewRecord = ts_json:ejson_to_record(#ts_user{}, EJSON), - - case ts_user:update(NewRecord) of - ok -> make_json_200(YArg, NewRecord); - - no_record -> make_json_404(YArg, - [{status, "no_such_user"}, {see_docs, "/ts_api_doc/user#POST"}]); - - _Error -> make_json_500(YArg) - end. - -delete_user(YArg, Username) -> todo. - list_timelines(YArg, Username) -> % pull out the POST data PostData = yaws_api:parse_post(YArg), @@ -187,24 +218,27 @@ list_timelines(YArg, Username) -> {timelines, EJSONTimelines}]}), % return response - {content, "application/json", JSONReponse}. + {content, "application/json", JSONResponse}. get_timeline(YArg, Username, TimelineId) -> % look for timeline case ts_timeline:lookup(Username, TimelineId) of % no such timeline, return 404 - no_record -> make_json_404(YArg, [{status, "no_such_timeline"}]); + no_record -> make_json_404(YArg, [{status, "no such timeline"}]); % return the timeline data Timeline -> make_json_200(YArg, Timeline) end. -put_timeline(YArg, Username) -> +put_timeline(YArg, Username, TimelineId) -> % parse the request body - {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), + EJSON = parse_json_body(YArg), % parse into a Timeline record - NewRecord = ts_json:ejson_to_record(#ts_timeline{}, EJSON), + TR = ts_json:ejson_to_record(#ts_timeline{}, EJSON), + + % set username and timeline id + NewRecord = TR#ts_timeline{ref = {Username, TimelineId}}, % insert into the database case ts_timeline:new(NewRecord) of @@ -228,22 +262,32 @@ put_timeline(YArg, Username) -> post_timeline(YArg, Username, TimelineId) -> % parse the POST data - {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), + EJSON = parse_json_body(YArg), + %{struct, Fields} = EJSON, - % create the timeline record - NewRecord = ts_json:ejson_to_record(#ts_timeline{}, EJSON), + % parse into a timeline record + TR = ts_json:ejson_to_record(#ts_timeline{}, EJSON), + + % not supported right now, would require changing all the entry keys + % check to see if they are changing the timeline id + %NewTimelineId = case lists:keyfind(1, timeline_id, Fields) of + % {timeline_id, Field} -> list_to_atom(Field); + % false -> TimelineId end, + + % set username and timeline id + NewRecord = TR#ts_timeline{ref = {Username, TimelineId}}, case ts_timeline:update(NewRecord) of ok -> make_json_200(YArg, NewRecord); no_record -> make_json_404(YArg, - [{status, "no_such_timeline"}, + [{status, "no such timeline"}, {see_docs, "/ts_api_doc/timelines#POST"}]); _Error -> make_json_500(YArg) end. -delete_timeline(YArg, Username, TimelineId) -> {status, 405}. +delete_timeline(_YArg, _Username, _TimelineId) -> {status, 405}. list_entries(YArg, Username, TimelineId) -> % pull out the POST data @@ -254,7 +298,7 @@ list_entries(YArg, Username, TimelineId) -> lists:keyfind(byDate, 1, PostData)} of {no_record, _ByDateField} -> make_json_404( - [{status, "no_such_timeline"}, + [{status, "no such timeline"}, {see_docs, "/ts_api_doc/entries#LIST"}]); % listing by date range @@ -275,7 +319,7 @@ list_entries(YArg, Username, TimelineId) -> end, % read sort order and list entries - Entries = case lists:keyfind(order, 1, PostData) -> + Entries = case lists:keyfind(order, 1, PostData) of % descending sort order {order, "desc"} -> ts_entry:list_desc( {Username, TimelineId}, StartDate, EndDate); @@ -292,7 +336,7 @@ list_entries(YArg, Username, TimelineId) -> {status, "ok"}, {entries, EJSONEntries}]}), - {content, "application/json", JSONResponse; + {content, "application/json", JSONResponse}; % listing by table position _Other -> @@ -326,13 +370,13 @@ list_entries(YArg, Username, TimelineId) -> {status, "ok"}, {entries, EJSONEntries}]}), - {content, "application/json", JSONResponse + {content, "application/json", JSONResponse} end. get_entry(YArg, Username, TimelineId, EntryId) -> case ts_entry:lookup(Username, TimelineId, EntryId) of % no such entry - no_record -> make_json_404(YArg, [{status, "no_such_entry"}]); + no_record -> make_json_404(YArg, [{status, "no such entry"}]); % return the entry data Entry -> make_json_200(YArg, Entry) end. @@ -340,14 +384,13 @@ get_entry(YArg, Username, TimelineId, EntryId) -> put_entry(YArg, Username, TimelineId) -> % parse the request body - {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), - - % pull out the entry data - {struct, Fields} = EJSON, - EJSONEntry = lists:findkey(entry, 1, Fields), % TODO: check for errors + EJSON = parse_json_body(YArg), % parse into ts_entry record - NewRecord = ts_json:ejson_to_record(#ts_entry{}, EJSONEntry), + ER = ts_json:ejson_to_record(#ts_entry{}, EJSON), + + % set username and timeline id + NewRecord = ER#ts_entry{ref = {Username, TimelineId, undef}}, case ts_entry:new(NewRecord) of % record created @@ -362,7 +405,7 @@ put_entry(YArg, Username, TimelineId) -> {see_docs, "/ts_api_doc/entries#PUT"} ]}), - {content, "application/json", JSONResposne}; + {content, "application/json", JSONResponse}; _Error -> make_json_500(YArg) end. @@ -370,25 +413,24 @@ put_entry(YArg, Username, TimelineId) -> post_entry(YArg, Username, TimelineId, EntryId) -> % parse the POST data - {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), - - % pull out the entry data - {struct, Fields} = EJSON, - EJSONEntry = lists:findkey(entry, 1, Fields), % TODO: error handling + EJSON = parse_json_body(YArg), % parse into ts_entry record - NewRecord = ts_json:ejson_to_record(#ts_entry{}, EJSONEntry), + ER = ts_json:ejson_to_record(#ts_entry{}, EJSON), + + % set uername, timeline id, and entry id + NewRecord = ER#ts_entry{ref = {Username, TimelineId, EntryId}}, case ts_entry:update(NewRecord) of ok -> make_json_200(YArg, NewRecord); no_record -> make_json_404(YArg, - [{status, "no_such_entry"}, {see_docs, "/ts_api_doc/entries#POST"}]); + [{status, "no such entry"}, {see_docs, "/ts_api_doc/entries#POST"}]); _Error -> make_json_500(YArg) end. -delete_entry(YArg, Username, TimelineId, EntryId) -> todo. +delete_entry(_YArg, _Username, _TimelineId, _EntryId) -> todo. % ============================== % % ======== UTIL METHODS ======== % @@ -398,12 +440,21 @@ delete_entry(YArg, Username, TimelineId, EntryId) -> todo. path_element_to_atom(PE) -> list_to_atom(re:replace(PE, "\\s", "_", [{return, list}])). +parse_json_body(YArg) -> + case catch json:decode([], binary_to_list(YArg#arg.clidata)) of + {done, {ok, EJSON}, _} -> EJSON; + Error -> + % TODO: log error internally + io:format("~p", [Error]), + throw(make_json_400(YArg)) + end. + %% Create a JSON 200 response. -make_json_200(YArg, Record) -> +make_json_200(_YArg, Record) -> EJSONRecord = ts_json:record_to_ejson(Record), Tag = case element(1, Record) of ts_user -> user; - ts_timeilne -> timeline; + ts_timeline -> timeline; ts_entry -> entry end, JSONResponse = json:encode({struct, [ @@ -413,12 +464,31 @@ make_json_200(YArg, Record) -> {content, "application/json", JSONResponse}. +make_json_400(YArg) -> make_json_400(YArg, []). +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})}]. + +make_json_401(YArg) -> make_json_401(YArg, []). +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})}]. + %% Create a JSON 404 response. make_json_404(YArg) -> make_json_404(YArg, []). make_json_404(YArg, Fields) -> % add default status if not provided F1 = case lists:keyfind(status, 1, Fields) of - false -> Fields ++ [{status, "not_found"}]; + false -> Fields ++ [{status, "not found"}]; _Else -> Fields end, @@ -442,6 +512,5 @@ make_json_405(YArg, Fields) -> make_json_500(_YArg) -> EJSON = {struct, [ - {status, "error"}, - {error, "Internal Server Error"}]}, + {status, "internal server error"}]}, [{status, 500}, {content, "application/json", json:encode(EJSON)}]. diff --git a/src/ts_api_session.erl b/src/ts_api_session.erl new file mode 100644 index 0000000..f55e0cb --- /dev/null +++ b/src/ts_api_session.erl @@ -0,0 +1,58 @@ +-module(ts_api_session). +-compile(export_all). + +-include("ts_db_records.hrl"). +-include("yaws_api.hrl"). + +new(Username) -> + DateTime = calendar:now_to_universal_time(erlang:now()), + Seconds = calendar:datetime_to_gregorian_seconds(DateTime), + + Session = #ts_api_session{ + username = Username, + expires = Seconds + 600 % timeout is 10 minutes + }, + + CookieVal = yaws_api:new_cookie_session(Session), + {CookieVal, Session}. + +logout(CookieVal) -> + yaws_api:delete_cookie_session(CookieVal). + +get_session(YArg) -> + % get the cookie header + Cookie = (YArg#arg.headers)#headers.cookie, + + % get the current server time + Now = calendar:now_to_universal_time(erlang:now()), + NowSeconds = calendar:datetime_to_gregorian_seconds(Now), + + % look up the cookie in the session server + case yaws_api:find_cookie_val("ts_api_session", Cookie) of + % no cookie, not logged in + [] -> not_logged_in; + + % found the cookie + CookieVal -> + % get the session data + case yaws_api:cookieval_to_opaque(CookieVal) of + {error, _} -> not_logged_in; + + {ok, Session} -> + if + % if the cookie has expired + NowSeconds > Session#ts_api_session.expires -> + logout(CookieVal), + session_expired; + + % cookie is fresh + true -> + % update the expiry time + NewSession = Session#ts_api_session{expires = NowSeconds + 500}, + yaws_api:replace_cookie_session(CookieVal, NewSession), + + % return cookie + NewSession + end + end + end. diff --git a/src/ts_db_records.hrl b/src/ts_db_records.hrl index 4bdcc98..2d9f355 100644 --- a/src/ts_db_records.hrl +++ b/src/ts_db_records.hrl @@ -19,3 +19,13 @@ mark, % String description of entry notes % String with further notes about the entry }). + +-record(ts_api_session, { + username, + expires +}). + +%-record(ts_session, { + %session_id, + %expires, + %username diff --git a/src/ts_entry.erl b/src/ts_entry.erl index 2efe4b9..beaf2d9 100644 --- a/src/ts_entry.erl +++ b/src/ts_entry.erl @@ -50,7 +50,7 @@ when is_integer(Start) and is_integer(Length) -> % return only the range selected. % TODO: can we do this without selecting all entries? - lists:sublist(SortedEntries, Start, Length); + lists:sublist(SortedEntries, Start + 1, Length); list({Username, Timeline}, StartDateTime, EndDateTime, OrderFun) -> diff --git a/src/ts_json.erl b/src/ts_json.erl index 4fa70ac..2e25576 100644 --- a/src/ts_json.erl +++ b/src/ts_json.erl @@ -43,39 +43,19 @@ encode_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) -> lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ", [Year, Month, Day, Hour, Minute, Second])). -ejson_to_record(_Empty=#ts_user{}, EJSON) -> - {struct, Fields} = EJSON, - - Pwd = case lists:keyfind(password, 1, Fields) of - false -> uninit; Field -> element(2, Field) end, - - #ts_user{ - username = element(2, lists:keyfind(username, 1, Fields)), - pwd = Pwd, - pwd_salt = uninit, - name = element(2, lists:keyfind(name, 1, Fields)), - email = element(2, lists:keyfind(email, 1, Fields)), - join_date = decode_datetime( - element(2, lists:keyfind(join_date, 1, Fields)))}; - ejson_to_record(_Empty=#ts_timeline{}, EJSON) -> {struct, Fields} = EJSON, - Username = element(2, lists:keyfind(username, 1, Fields)), - TimelineId = element(2, lists:keyfind(timeline_id, 1, Fields)), #ts_timeline{ - ref = {Username, TimelineId}, + ref = {undef, undef}, created = decode_datetime(element(2, lists:keyfind(created, 1, Fields))), desc = element(2, lists:keyfind(description, 1, Fields))}; ejson_to_record(_Empty=#ts_entry{}, EJSON) -> {struct, Fields} = EJSON, - Username = element(2, lists:keyfind(username, 1, Fields)), - TimelineId = element(2, lists:keyfind(timeline_id, 1, Fields)), - EntryId = element(2, lists:keyfind(entry_id, 1, Fields)), #ts_entry{ - ref = {Username, TimelineId, EntryId}, + ref = {undef, undef, undef}, timestamp = calendar:datetime_to_gregorian_seconds(decode_datetime( element(2, lists:keyfind(timestamp, 1, Fields)))), mark = element(2, lists:keyfind(mark, 1, Fields)), diff --git a/src/ts_user.erl b/src/ts_user.erl index fd43412..5e3fd7c 100644 --- a/src/ts_user.erl +++ b/src/ts_user.erl @@ -32,12 +32,16 @@ list(Start, Length) -> ts_common:list(ts_user, Start, Length). hash_input_record(User=#ts_user{}) -> % create a new User record - {HashedPwd, Salt} = hash_pwd(User#ts_user.pwd), + Salt = generate_salt(), + HashedPwd = hash_pwd(User#ts_user.pwd, Salt), User#ts_user{pwd = HashedPwd, pwd_salt = Salt}. generate_salt() -> crypto:rand_bytes(36). -hash_pwd(Password) -> - Salt = generate_salt(), - Hashed = crypto:sha(Password ++ Salt), - {Hashed, Salt}. +hash_pwd(Password, Salt) -> crypto:sha(Password ++ Salt). + +check_credentials(Username, Password) -> + User = lookup(Username), + HashedInput = hash_pwd(Password, User#ts_user.pwd_salt), + + HashedInput == User#ts_user.pwd. diff --git a/yaws.conf b/yaws.conf index c9aed63..4b0061c 100644 --- a/yaws.conf +++ b/yaws.conf @@ -5,6 +5,7 @@ runmod = timestamper port = 8000 - listen = /home/jdbernard/projects/timestamper/web-app/www - appmods = timestamper_api + listen = 127.0.0.1 + docroot = /home/jdbernard/projects/timestamper/web-app/www + appmods = ts_api