n4u_session.erl 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. -module(n4u_session).
  2. -include_lib("n4u/include/n4u.hrl").
  3. -include_lib("stdlib/include/ms_transform.hrl"). % todo read how this works
  4. -export([init/2, finish/2]).
  5. -export([ensure_sid/3, session_sid/2, session_sid/4, expired/2,
  6. lookup_ets/1, clear/0, clear/1, cookie_expire/1, ttl/0, till/2,
  7. new_sid/0, new_cookie_value/1, new_cookie_value/2,
  8. session_cookie_name/1, set_session_value/3, set_value/2,
  9. invalidate_sessions/0, get_value/2, get_value/3, remove_value/1]).
  10. % todo check where used
  11. init(State, Ctx) ->
  12. case application:get_env(n4u, auto_session, "") of
  13. disabled -> {ok, State, Ctx};
  14. _ -> ?MODULE:ensure_sid(State, Ctx, [])
  15. end.
  16. finish(State, Ctx) -> {ok, State, Ctx}.
  17. ensure_sid(State, Ctx, []) -> ensure_sid(State, Ctx, site);
  18. ensure_sid(State, Ctx, From) ->
  19. Cookie_Name = nitro:to_atom(session_cookie_name(From)),
  20. Session_Id = wf:cookie_req(Cookie_Name, Ctx#cx.req),
  21. wf:info(?MODULE, "Ensure SID ~p-sid=~p~n", [From, Session_Id]),
  22. session_sid(State, Ctx, Session_Id, From).
  23. session_sid(SID, Source) -> session_sid([], ?CTX, SID, Source).
  24. session_sid(State, Ctx, Session_Id, From) ->
  25. wf:info(?MODULE, "Session Init ~p: ~p~n", [From, Session_Id]),
  26. Lookup = lookup_ets({Session_Id, <<"auth">>}),
  27. New_Till = till(calendar:local_time(), ttl()),
  28. Session_Cookie = case Lookup of
  29. undefined ->
  30. Cookie_Value = case Session_Id of
  31. undefined ->
  32. case wf:qc(application:get_env(n4u, transfer_session, <<"csid">>), Ctx) of
  33. undefined -> new_cookie_value(From);
  34. Csid -> new_cookie_value(Csid, From)
  35. end;
  36. _ ->
  37. new_cookie_value(Session_Id, From)
  38. end,
  39. Cookie = {{Cookie_Value, <<"auth">>}, <<"/">>, os:timestamp(), New_Till, new},
  40. ets:insert(cookies, Cookie),
  41. wf:info(?MODULE, "Auth Cookie New: ~p~n", [Cookie]),
  42. Cookie;
  43. {{Session, Key}, Path, Issued, Till, Status} ->
  44. case expired(Issued, Till) of
  45. false ->
  46. Cookie = {{Session, Key}, Path, Issued, Till, Status},
  47. wf:info(?MODULE, "Auth Cookie Same: ~p~n", [Cookie]),
  48. Cookie;
  49. true ->
  50. Cookie = {{new_cookie_value(From), <<"auth">>}, <<"/">>, os:timestamp(), New_Till, new},
  51. clear(Session),
  52. ets:insert(cookies, Cookie),
  53. wf:info(?MODULE, "Auth Cookie Expired in Session ~p~n", [Session]),
  54. Cookie
  55. end;
  56. What ->
  57. wf:info(?MODULE, "Auth Cookie Error: ~p~n", [What]),
  58. What
  59. end,
  60. {{ID, _}, _, _, _, _} = Session_Cookie,
  61. erlang:put(session_id, ID),
  62. wf:info(?MODULE, "State: ~p~n", [Session_Cookie]),
  63. {ok, State, Ctx#cx{session = Session_Cookie}}.
  64. expired(_Issued, Till) ->
  65. Till < calendar:local_time().
  66. lookup_ets(Key) ->
  67. Res = ets:lookup(cookies, Key),
  68. %wf:info(?MODULE, "Lookup ETS: ~p", [{Res, Key}]),
  69. case Res of
  70. [] -> undefined;
  71. [Value] -> Value;
  72. Values -> Values
  73. end.
  74. clear() -> clear(erlang:get(session_id)).
  75. clear(Session) ->
  76. [ets:delete(cookies, X) || X <- ets:select(cookies, ets:fun2ms(fun(A)
  77. when (erlang:element(1, erlang:element(1, A)) == Session) -> erlang:element(1, A) end))].
  78. cookie_expire(Seconds_To_Live) ->
  79. Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
  80. DateTime = calendar:gregorian_seconds_to_datetime(Seconds + Seconds_To_Live),
  81. cow_date:rfc2109(DateTime).
  82. ttl() -> application:get_env(n4u, ttl, 60 * 15).
  83. till(Now, TTL) ->
  84. calendar:gregorian_seconds_to_datetime(
  85. calendar:datetime_to_gregorian_seconds(Now) + TTL).
  86. new_sid() ->
  87. nitro:hex(binary:part(
  88. crypto:mac(application:get_env(n4u, mac_type, hmac),
  89. application:get_env(n4u, mac_subtype, sha256),
  90. n4u_secret:secret(),
  91. erlang:term_to_binary(os:timestamp())),
  92. 0, 16)).
  93. new_cookie_value(From) -> new_cookie_value(new_sid(), From).
  94. new_cookie_value(undefined, From) -> new_cookie_value(new_sid(), From);
  95. new_cookie_value(Session_Key, From) ->
  96. F = nitro:f("document.cookie='~s=~s; path=/; expires=~s';",
  97. [nitro:to_list(session_cookie_name(From)),
  98. nitro:to_list(Session_Key),
  99. cookie_expire(2147483647)]),
  100. wf:info(?MODULE, "Cookie: ~p~n", [F]),
  101. nitro:wire(F),
  102. % NOTE: Infinity-expire cookie will allow to clean up all session cookies
  103. % by request from browser so we don't need to sweep them on server.
  104. % Actually we should anyway to cleanup outdated cookies
  105. % that will never be requested.
  106. Session_Key.
  107. session_cookie_name([]) -> session_cookie_name(site);
  108. session_cookie_name(From) -> nitro:to_binary([nitro:to_binary(From), <<"-sid">>]).
  109. set_session_value(Session, Key, Value) ->
  110. Till = till(calendar:local_time(), ttl()),
  111. ets:insert(cookies, {{Session, Key}, <<"/">>, os:timestamp(), Till, Value}),
  112. Value.
  113. set_value(Key, Value) ->
  114. New_Till = till(calendar:local_time(), ttl()),
  115. ets:insert(cookies, {{erlang:get(session_id), Key}, <<"/">>, os:timestamp(), New_Till, Value}),
  116. Value.
  117. invalidate_sessions() ->
  118. ets:foldl(fun(X, A) ->
  119. {Sid, Key} = erlang:element(1, X),
  120. ?MODULE:get_value(Sid, Key, undefined),
  121. A
  122. end, 0, cookies).
  123. get_value(Key, Default_Value) ->
  124. get_value(erlang:get(session_id), Key, Default_Value).
  125. get_value(SID, Key, Default_Value) ->
  126. Res = case lookup_ets({SID, Key}) of
  127. undefined -> Default_Value;
  128. {{SID, Key}, _, Issued, Till, Value} ->
  129. case expired(Issued, Till) of
  130. false -> Value;
  131. true ->
  132. ets:delete(cookies, {SID, Key}),
  133. Default_Value
  134. end
  135. end,
  136. %wf:info(?MODULE, "Session Lookup Key ~p Value ~p~n", [Key, Res]),
  137. Res.
  138. remove_value(Key) -> ets:delete(cookies, Key).