n2o Erlang framework email authentication - erlang

I'm new to Erlang and N2O but have some experience in python web development. I want to create authentication by email address (email - password) in my application instead of using AVZ. I've created Sign Up page with this code (other code is as n2o_sample). But instead of putting user to kvs I have {error, no_container}
-module(signup_page).
-compile(export_all).
-include_lib("n2o/include/wf.hrl").
-include_lib("n2o_sample/include/users.hrl").
title() -> [ <<"Sign Up Page">> ].
main() -> #dtl{file = "login", app = n2o_sample,
bindings = [{title,title()},{body,body()}]}.
body() ->
[ #span{id=display}, #br{},
#span{body="Email: "}, #textbox{id=user}, #br{},
#span{body="Password: "}, #password{id=pass}, #br{},
#span{body="Confirm Password"}, #password{id=confirm}, #br{},
#button{id=signup, body="Sign Up",
postback=signup,source=[user,pass,confirm]}].
event(signup) ->
User = wf:q(user), Password = wf:q(pass), Confirm = wf:q(confirm),
U = kvs:get(user, User, undefined),
case U of
{ok, undefined} ->
case Password of
undefined -> wf:update(display, <<"No pass">>);
Confirm -> Status = kvs:put(#user{id=User}),
% -> {error, no_container}
io:format("~w~n", [Status]);
B -> io:format("~w~n", [B]) end;
A -> io:format("~w~n", [A]),
wf:update(display, <<"Already registered!">>) end;
event(_) -> [].

You do everything right.
The question is essentially with KVS configuring. To make KVS properly working you should do several steps:
1. Put the kvs into rebar.config
{kvs, ".*", {git, "git://github.com/synrc/kvs", {tag,"1.5.0"}}},
Use frozen tag version, e.g. "1.5.0" is the latest stable.
2. Configure KVS application in sys.config
{n2o, [{port,8000},{transition_port, 8000}]},
{kvs, [{dba,store_mnesia},
{schema, [kvs_user, kvs_acl, kvs_feed, kvs_subscription ]} ]},
I put in example N2O configuring to see where is should be placed.
3. Just after first launch of "make console" you should initialize the DB in Erlang shell:
1> kvs:join().
This will instantiate MNESIA tables.
4. After that in Erlang shell please do the check of your problem:
2> rr(kvs_user).
First load all records from kvs_user module.
Then perform your check:
3> kvs:put(#user{id="maxim#synrc.com"}).

Related

Erlang: Chat server crashing on startup

I got a set of tests that the program should pass and all the local tests works just fine with my server it's when I try to run the remote tests that the server crashes.
The crash message is the following:
=ERROR REPORT==== 23-Jul-2015::23:59:17 === Error in process <0.39.0> on
node 'nodeS#127.0.0.1' with exit value:
{undef,[{genserver,start,[server, {server_st,[],[]},#Fun<server.loop.2>],[]}]}
My start-up function looks as following:
loop(St, {From, Nick, connection_wanted}) ->
case lists:keymember(Nick, 2, St#server_st.users) of
false -> {ok, St#server_st{users = St#server_st.users ++ [{From, Nick}]}};
true -> {{user_already_connected, St}, St}
end;
With the record "server_st" is defined as:
-record(server_st, {users = [], channels = []}).
Finally the genserver start&loop function is:
start(Name, State, F) ->
Pid = spawn(fun() -> loop(State, F) end),
register(Name, Pid),
Pid.
loop(State, F) ->
receive
{request, From, Ref, Data} ->
case catch(F(State, Data)) of
{'EXIT', Reason} ->
From!{exit, Ref, Reason},
loop(State, F);
{R, NewState} ->
From!{result, Ref, R},
loop(NewState, F)
end;
{update, From, Ref, NewF} ->
From ! {ok, Ref},
loop(State, NewF);
stop ->
true
end.
Then genserver functions I'm not allowed to change. If needed I can post the whole testsuite too.
Edit
Digging a bit further into the test cases and I'm unsure if it really is the server that's causing the issue, my remote connect function looks as following:
loop(St, {connect, {_Server, _Machine}}) ->
ServerPID = {list_to_atom(_Server), list_to_atom(_Machine)},
case genserver:request(ServerPID, {self(), St#cl_st.nick, connection_wanted}) of
ok -> {ok, St#cl_st{connected_to = ServerPID}};
_ -> {{error, user_already_connected, "A user with the nick " ++ St#cl_st.nick ++ "is already connected to" ++ _Server}, St}
end;
Edit 2
Found the specific row inside the testsuite that's causing the error:
-define(HOST, '127.0.0.1').
new_client(Nick, GUIName) ->
ClientName = test_client:find_unique_name("client_"),
ClientAtom = list_to_atom(ClientName),
% Row below is causing the error
Result = slave:start(?HOST, ClientAtom),
assert_ok("start client node "++ClientName, element(1,Result)),
ClientNode = element(2,Result),
InitState = client:initial_state(Nick, GUIName),
Result2 = spawn(ClientNode, genserver, start, [ClientAtom, InitState, fun client:loop/2]),
assert("client startup "++ClientName, is_pid(Result2)),
{Nick, ClientAtom, ClientNode}.
Your function genserver:start/3 is most probably not exported or module genserver is not available at the node where you run code which calls it.
Solved it, was in a completely unrelated part where the client is communicating with other users. Still used the whereis command to locate other users from an older version of the program.

What am I doing wrong with erl_parse:parse_form?

I wrote a Hello-World module and compiled it successfully. And then I tried to learn the things under the hood by using erl_scan and erl_parse.
-module(useless).
-export([hello/0]).
hello() -> io:format("hello world\n").
and I type in the erl shell
{ok, S} = file:read_file("useless.erl").
and
{ok, T, _} = erl_scan:string(binary_to_list(S)).
It works fine apparently. But when I try erl_parse:parse_form(T).
It gives {error,{2,erl_parse,["syntax error before: ","'-'"]}}
What am I doing wrong?
Edit:
the module compile is helpful.
Also this is cool.
The function erl_parse:parse_form/1 works on one form only. So you must split the result of erl_scan:string/1 into individual forms first.
you can use erl_scan:tokens to achieve this (this code wors, but I am not sure I use the function in the right way):
-module(useless).
-export([eval/1]).
eval(File) ->
{ok, B} = file:read_file(File),
Forms = scan(erl_scan:tokens([],binary_to_list(B),1),[]),
F = fun(X) -> {ok,Y} = erl_parse:parse_form(X), Y end,
[F(X) || X <- Forms].
scan({done,{ok,T,N},S},Res) ->
scan(erl_scan:tokens([],S,N),[T|Res]);
scan(_,Res) ->
lists:reverse(Res).
Robert suggestion is to use the re-entrant feature of erl_scan:tokens/3.
The docs on this function are not explicit and it took me some time to understand that the initial string needed to be closed by eof. (if not the tokens function runs an endless loop).
Here is the code I finally came up with, I hope that will help others.
eval(File)->
{ok, Data} = file:read_file(File),
String=erlang:binary_to_list(Data),
scan(String).
scan(String) when is_list(String)->
scan(String++eof,[]). %% appended eof
scan({done, Result, LeftOverChars},Acc)->
scan_done(Result,LeftOverChars,Acc);
scan({more, Continuation},Acc)->
scan(erl_scan:tokens(Continuation,[],1),Acc);
scan(String,Acc) when is_list(String)->
scan(erl_scan:tokens([],String,1),Acc).
scan_done({error,ErrorMsg,_Location},_LeftOverChars,_Acc)->
ErrorMsg;
scan_done({eof,Location},LeftOverChars,Acc)->
Acc;
scan_done({ok,Tokens,Location},LeftOverChars,Acc)->
case erl_parse:parse_form(Tokens) of
{ok,R}->scan(LeftOverChars,Acc++[R]);
{error,R}->scan(LeftOverChars,R)
end.

Rebar distributed tests

I have some issue with distributed tests under rebar.
Rebar starts node with name nonode#nohost. After it I call help function make_distrib which provide normal node name and starts distribution work.
After starting the slave node I couldn't sent to it any lambda. I had error:
=ERROR REPORT==== 27-Jul-2013::22:48:02 ===
Error in process on node 'test1#just' with exit value: {{badfun,#Fun<msg_proxy_tests.2.117197241>},[{error_handler,undefined_lambda,3,[{file,"error_handler.erl"},{line,64}]}]}
But! If I run it test with simple way - all works good:
$erl
1> c(msg_proxy_tests).
{ok,msg_proxy_tests}
2> eunit:test({module, msg_proxy_tests},[verbose]).
======================== EUnit ========================
module 'msg_proxy_tests'msg_proxy_tests: distrib_mode_test_ (distribute mode test for nodes)...
msg_proxy_tests.erl:14:<0.48.0>: nodes ( ) = [test1#just]
msg_proxy_tests.erl:15:<9999.39.0>: node ( ) = test1#just
msg_proxy_tests.erl:17:<0.48.0>: nodes ( ) = [test1#just]
[0.238 s] ok
How can I fix this?
Module source:
-module(msg_proxy_tests).
-include_lib("eunit/include/eunit.hrl").
distrib_mode_test_()->
{"distribute mode test for nodes", timeout, 60,
fun() ->
{ok, Host} = inet:gethostname(),
make_distrib("tests#"++Host, shortnames),
slave:start(Host, test1),
?debugVal(nodes()),
spawn(list_to_atom("test1#"++Host), fun()-> ?debugVal(node()) end),
timer:sleep(100),
?debugVal(nodes()),
stop_distrib(),
ok
end}.
-spec make_distrib( NodeName::string()|atom(), NodeType::shortnames | longnames) ->
{ok, ActualNodeName::atom} | {error, Reason::term()}.
make_distrib(NodeName, NodeType) when is_list(NodeName) ->
make_distrib(erlang:list_to_atom(NodeName), NodeType);
make_distrib(NodeName, NodeType) ->
case node() of
'nonode#nohost' ->
[] = os:cmd("epmd -daemon"),
case net_kernel:start([NodeName, NodeType]) of
{ok, _Pid} -> node()
end;
CurrNode -> CurrNode
end.
stop_distrib()->
net_kernel:stop().
That is because of module hash on both nodes is different. That happens because eunit compiles code before it run and module contains -ifdef(TEST) macroses, which will definitely change it's hash.
Thats why anonymous function #Fun<msg_proxy_tests.2.117197241 could not be called and error occurred. (look at function name and you will notice funny numbers, this is module hash, it will be different on both nodes).
If you want to avoid this, you should call funs by it fully qualified name: module:fun_name/arity.

Eunit test won't wait for receive

Eunit won't wait for the receive, is there something special to eunit.
-module (test_account).
-include_lib ("eunit/include/eunit.hrl").
-compile (export_all).
login_test() ->
{ok, Socket} = gen_tcp:connect("localhost", 5678,
[binary, {packet, 4}]),
RoleName = <<"abc">>,
LenRoleName = byte_size(RoleName),
Password = <<"def">>,
LenPassword = byte_size(Password),
LoginBin = <<11001:16, LenRoleName:16, RoleName/binary,
LenPassword:16, Password/binary>>,
gen_tcp:send(Socket, LoginBin),
print(Socket).
print(Socket) ->
receive
{tcp, Socket, Data} ->
io:format("Data=~p~n", [Data])
end.
If I call test_account:login_test(). directly, it can receive the response.
Thank you.
My guess is there's something wrong on the listening side, like missing {packet, 4} parameter or something. I started a listening socket on the required port manually, and the test did work.
EUnit isn't really supposed to run integration tests out-of-the-box (though there are some libraries that make it somewhat convenient for integration tests as well). What you are realy supposed to do here is something like that:
main_test_() ->
{setup,
fun setup/0,
fun teardown/1,
[{"login", fun login_test/0}]
}.
setup() ->
process_flag(trap_exit, true),
{ok, Pid} = my_tcp_server:start_link(),
Pid.
teardown(Pid) ->
exit(Pid, shutdown).
So, basically, you shouldn't rely on a separately running server when using just EUnit. Instead, you should either start it explicitly from your code or, if it's external, mock it (or just some of its parts, if the entire server code is complicated).
P.S. Don't forget the underscore at the end of 'main_test_', it's a contract for {setup, ...} and {foreach, ...} tests.

Get field from mnesia

I have a mnesia table users with user and password field.
Data from my table:
[{users, <<"user_name">>, <<"password">>}].
I need to get password by user name. I make:
mnesia:dirty_read({users, <<"user_name">>}).
But it returns [].
How can I get the password by user name?
You didn't specify the record syntax you are using, but it looks like
-record(users, {username, password}).
... or something similar. So, assuming that, when you created the table, did you do anything special to set the id? In this example "username" (the first entry in the users record) should be the id by default, unless you did something special.
If you continue to have issues, consider using mnesia:match_object/1 or /3. You specify, in pattern / record syntax the portion you have to match (in this case, the username), and use ='' to match anything you don't know or care about (in this case, that'd be the password portion).
I hope that helps!
You can do something like below:
YourPasswd = mnesia:ets(fun()-> mnesia:dirty_read({users,UserId}) end),
case YourPasswd of
[] -> {error, 'No User Found'};
[{users,_UserID,Passwd}] ->
{success, Passwd}
end.
Hope data is correctly written into mnesia :)
look at this function:
-define(TABLE_NAME,users).
get_password_by_username(Username)->
F = fun(U)-> mnesia:read({?TABLE_NAME,U}) end,
mnesia:activity(transaction,F,[Username],mnesia_frag).
That will give you the result. The good thing with mnesia:activity/4 is that wether the table is fragmented or not, the answers are okay.
good luck
I understand you want your passwords checked as quickly as possible, but is your project at the stage where you need to optimize this?
I've an authentication module in one of my projects and I have a similar goals as yours. As I haven't had occasion to optimize just yet, I am using mnsesia transactions and lists for user names and passwords.
Here is part of my auth module.
-module(auth).
-export([init/1, add_user/2, start_session/2]).
-record(auth_user, {username, password}).
init(Nodes) ->
mnesia:create_table(auth_user,
[{disc_copies, Nodes},
{attributes, record_info(fields, auth_user)}]).
add_user(Username, Password) ->
T = fun() ->
mnesia:write(#auth_user {
username = Username,
password = Password})
end,
mnesia:transaction(T).
start_session(Username, Password) ->
T = fun() ->
mnesia:read(auth_user, Username)
end,
{atomic, Ret} = mnesia:transaction(T),
case Ret of
[U] ->
if (U#auth_user.password == Password) ->
true;
true ->
false
end;
_Else ->
false
end.
After compiling and starting up the erlang shell.
Eshell V5.8.3 (abort with ^G)
1> mnesia:create_schema([node()]).
ok
3> mnesia:start().
ok
4> auth:init([node()]).
{atomic,ok}
5> auth:add_user("rodericktaylor", "password").
{atomic,ok}
6> true = auth:start_session("rodericktaylor", "password").
true
7>
To see if I get the same issue you are having, I switched to binary values and did dirty reads.
start_session_dirty(Username, Password) ->
case mnesia:dirty_read(auth_user, Username) of
[U] ->
if (U#auth_user.password == Password) ->
true;
true ->
false
end;
_Else ->
false
end.
The following commands in the erl shell show that It works how you expect it to.
12> auth:add_user(<<"rodericktaylor">>, <<"binarypassword">>).
{atomic,ok}
14> true = auth:start_session_dirty(<<"rodericktaylor">>, <<"binarypassword">>).
true
15>
I hope I've helped.

Resources