123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- -module(n4u_session).
- -include_lib("n4u/include/n4u.hrl").
- -include_lib("stdlib/include/ms_transform.hrl"). % todo read how this works
- -export([init/2, finish/2]).
- -export([ensure_sid/3, session_sid/2, session_sid/4, expired/2,
- lookup_ets/1, clear/0, clear/1, cookie_expire/1, ttl/0, till/2,
- new_sid/0, new_cookie_value/1, new_cookie_value/2,
- session_cookie_name/1, set_session_value/3, set_value/2,
- invalidate_sessions/0, get_value/2, get_value/3, remove_value/1]).
- % todo check where used
- init(State, Ctx) ->
- case application:get_env(n4u, auto_session, "") of
- disabled -> {ok, State, Ctx};
- _ -> ?MODULE:ensure_sid(State, Ctx, [])
- end.
- finish(State, Ctx) -> {ok, State, Ctx}.
- ensure_sid(State, Ctx, []) -> ensure_sid(State, Ctx, site);
- ensure_sid(State, Ctx, From) ->
- Cookie_Name = nitro:to_atom(session_cookie_name(From)),
- Session_Id = wf:cookie_req(Cookie_Name, Ctx#cx.req),
- wf:info(?MODULE, "Ensure SID ~p-sid=~p~n", [From, Session_Id]),
- session_sid(State, Ctx, Session_Id, From).
- session_sid(SID, Source) -> session_sid([], ?CTX, SID, Source).
- session_sid(State, Ctx, Session_Id, From) ->
- wf:info(?MODULE, "Session Init ~p: ~p~n", [From, Session_Id]),
- Lookup = lookup_ets({Session_Id, <<"auth">>}),
- New_Till = till(calendar:local_time(), ttl()),
-
- Session_Cookie = case Lookup of
- undefined ->
-
- Cookie_Value = case Session_Id of
- undefined ->
- case wf:qc(application:get_env(n4u, transfer_session, <<"csid">>), Ctx) of
- undefined -> new_cookie_value(From);
- Csid -> new_cookie_value(Csid, From)
- end;
-
- _ ->
- new_cookie_value(Session_Id, From)
- end,
-
- Cookie = {{Cookie_Value, <<"auth">>}, <<"/">>, os:timestamp(), New_Till, new},
- ets:insert(cookies, Cookie),
- wf:info(?MODULE, "Auth Cookie New: ~p~n", [Cookie]),
- Cookie;
-
- {{Session, Key}, Path, Issued, Till, Status} ->
- case expired(Issued, Till) of
- false ->
- Cookie = {{Session, Key}, Path, Issued, Till, Status},
- wf:info(?MODULE, "Auth Cookie Same: ~p~n", [Cookie]),
- Cookie;
- true ->
- Cookie = {{new_cookie_value(From), <<"auth">>}, <<"/">>, os:timestamp(), New_Till, new},
- clear(Session),
- ets:insert(cookies, Cookie),
- wf:info(?MODULE, "Auth Cookie Expired in Session ~p~n", [Session]),
- Cookie
- end;
-
- What ->
- wf:info(?MODULE, "Auth Cookie Error: ~p~n", [What]),
- What
- end,
-
- {{ID, _}, _, _, _, _} = Session_Cookie,
- erlang:put(session_id, ID),
- wf:info(?MODULE, "State: ~p~n", [Session_Cookie]),
- {ok, State, Ctx#cx{session = Session_Cookie}}.
- expired(_Issued, Till) ->
- Till < calendar:local_time().
- lookup_ets(Key) ->
- Res = ets:lookup(cookies, Key),
- %wf:info(?MODULE, "Lookup ETS: ~p", [{Res, Key}]),
- case Res of
- [] -> undefined;
- [Value] -> Value;
- Values -> Values
- end.
- clear() -> clear(erlang:get(session_id)).
- clear(Session) ->
- [ets:delete(cookies, X) || X <- ets:select(cookies, ets:fun2ms(fun(A)
- when (erlang:element(1, erlang:element(1, A)) == Session) -> erlang:element(1, A) end))].
- cookie_expire(Seconds_To_Live) ->
- Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
- DateTime = calendar:gregorian_seconds_to_datetime(Seconds + Seconds_To_Live),
- cow_date:rfc2109(DateTime).
- ttl() -> application:get_env(n4u, ttl, 60 * 15).
- till(Now, TTL) ->
- calendar:gregorian_seconds_to_datetime(
- calendar:datetime_to_gregorian_seconds(Now) + TTL).
- new_sid() ->
- nitro:hex(binary:part(
- crypto:mac(application:get_env(n4u, mac_type, hmac),
- application:get_env(n4u, mac_subtype, sha256),
- n4u_secret:secret(),
- erlang:term_to_binary(os:timestamp())),
- 0, 16)).
- new_cookie_value(From) -> new_cookie_value(new_sid(), From).
- new_cookie_value(undefined, From) -> new_cookie_value(new_sid(), From);
- new_cookie_value(Session_Key, From) ->
- F = nitro:f("document.cookie='~s=~s; path=/; expires=~s';",
- [nitro:to_list(session_cookie_name(From)),
- nitro:to_list(Session_Key),
- cookie_expire(2147483647)]),
- wf:info(?MODULE, "Cookie: ~p~n", [F]),
- nitro:wire(F),
- % NOTE: Infinity-expire cookie will allow to clean up all session cookies
- % by request from browser so we don't need to sweep them on server.
- % Actually we should anyway to cleanup outdated cookies
- % that will never be requested.
- Session_Key.
- session_cookie_name([]) -> session_cookie_name(site);
- session_cookie_name(From) -> nitro:to_binary([nitro:to_binary(From), <<"-sid">>]).
- set_session_value(Session, Key, Value) ->
- Till = till(calendar:local_time(), ttl()),
- ets:insert(cookies, {{Session, Key}, <<"/">>, os:timestamp(), Till, Value}),
- Value.
- set_value(Key, Value) ->
- New_Till = till(calendar:local_time(), ttl()),
- ets:insert(cookies, {{erlang:get(session_id), Key}, <<"/">>, os:timestamp(), New_Till, Value}),
- Value.
- invalidate_sessions() ->
- ets:foldl(fun(X, A) ->
- {Sid, Key} = erlang:element(1, X),
- ?MODULE:get_value(Sid, Key, undefined),
- A
- end, 0, cookies).
- get_value(Key, Default_Value) ->
- get_value(erlang:get(session_id), Key, Default_Value).
- get_value(SID, Key, Default_Value) ->
- Res = case lookup_ets({SID, Key}) of
- undefined -> Default_Value;
- {{SID, Key}, _, Issued, Till, Value} ->
- case expired(Issued, Till) of
- false -> Value;
- true ->
- ets:delete(cookies, {SID, Key}),
- Default_Value
- end
- end,
- %wf:info(?MODULE, "Session Lookup Key ~p Value ~p~n", [Key, Res]),
- Res.
- remove_value(Key) -> ets:delete(cookies, Key).
|