I want to use session state in my Suave project.
I enable the session state for the "/home" url, and following this, I check that this state exists:
#r #"packages\Suave.0.32.0\lib\net40\Suave.dll"
open Suave // always open suave
open Suave.Http.Successful // for OK-result
open Suave.Web // for config
open Suave.Http
open Suave.State.CookieStateStore
open Suave.Http.Applicatives
open Suave.Types
[
path "/home" >>= statefulForSession >>= context (fun x ->
match HttpContext.state x with
| Some state -> OK "session found"
| None -> Suave.Http.RequestErrors.BAD_REQUEST "No Session Found"
)]
|> choose
|> startWebServer defaultConfig
Every time accessing "/home", it ends with BAD_REQUEST "No Session Found".
What am I doing wrong?
Related
I am trying to hide my whole application behind a authentication (I'll deal with authorization when this works), and for now I want to every url to require github login. I am not getting the github login page up.
I have tried to combine the SAFE-stack template and "Using OAuth with Saturn", but I do not get the github login page (which I do get when following only the Saturn guide), I just get the normal todo page. If I click the Add button, the server prints
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost:8085/api/ITodosApi/addTodo application/json; charset=UTF-8 68
info: Microsoft.AspNetCore.Authentication.OAuth.OAuthHandler`1[[Microsoft.AspNetCore.Authentication.OAuth.OAuthOptions, Microsoft.AspNetCore.Authentication.OAuth, Version=3.1.11.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]][12]
AuthenticationScheme: GitHub was challenged.
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 10.8057ms 302
And the item is not added to the list of todos.
I have tried shaving off everything except the authentication bit, and compared to a clean safe template, the only changes I have made is
dotnet paket add Saturn.Extensions.Authorization --project src/Server/Server.fsproj
Manually downgrade two dependencies in paket.lock that otherwise created an error: Microsoft.AspNetCore.Authentication.Google to (3.1.11) and Microsoft.AspNetCore.Authentication.OpenIdConnect to (3.1.11)
Change the app value in Server/Server.fs to the following (I created a new github auth app for this issue):
let loggedInPipeline = pipeline {
requires_authentication (Giraffe.Auth.challenge "GitHub")
}
let loggedInView = router {
pipe_through loggedInPipeline
get "/" webApp
}
let appRouter = router {
forward "" loggedInView
}
let app =
application {
use_router appRouter
url "http://0.0.0.0:8085/"
memory_cache
use_static "public"
use_gzip
use_github_oauth "8cde657dfd1d3a41b9ed" "0b245e12900ff8486ade076aae07aa0deb0fd83d" "/signin-github" [("login", "githubUsername"); ("name", "fullName")]
}
run app
My gitHub app config auth callback url: http://localhost:8080/signin-github
I got help on solving this, just for AzureAD. A blog post can be found here https://www.compositional-it.com/news-blog/safe-stack-authentication-with-active-directory-part-2/. Should be the same for github. What I needed to do was to make some changes to webpack.config.js, Server.fs and build.fsx
webpack.config.js
devServerProxy: {
// redirect all requests to the server on port 8085
'**': {
//...
var CONFIG = {
appHtmlTemplate: './src/Client/app.html',
//...
var commonPlugins = [
new HtmlWebpackPlugin({
filename: 'app.html',
//...
var CONFIG = {
// ... other webpack config settings
outputDir: './src/Server/public',
// ...
devServer: {
// ...other dev server config settings
writeToDisk: true
},
Server.fs:
let authScheme = "AzureAD"
let isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") = Environments.Development;
let noAuthenticationRequired nxt ctx = task { return! nxt ctx }
let authChallenge : HttpFunc -> HttpContext -> HttpFuncResult =
requiresAuthentication (Auth.challenge authScheme)
let routes =
choose [
route "/" >=> authChallenge >=> htmlFile "public/app.html"
]
build.fsx:
let serverPublicPath = Path.getFullName "./src/Server/public"
let clientPublicPath = Path.getFullName "./src/Client/public"
Target.create "Clean" (fun _ ->
Shell.cleanDir deployDir
Shell.cleanDir serverPublicPath)
Target.create "Run" (fun _ ->
Shell.copyDir serverPublicPath clientPublicPath FileFilter.allFiles
//... other commands
You will need to use a non-Chrome browser if you are working locally, such as Firefox, due to the cookie issue mentioned earlier.
It is a good idea to open a private browsing window, to make sure that you don't already have a logged in account etc.
If you are having issues, check that you have
properly set up your app's Active Directory registration in Azure
added the required AD configuration to your Server in appsettings.json, including the login / logout callback urls you set in the AD registration.
have you tried https instead of http for your url parameter?
Replace
url "http://0.0.0.0:8085"
with
url "https://0.0.0.0:8085"
This fixed the problem for me.
Demo code: https://github.com/functionalfriday/fsharp-saturn-demos
I'm a beginner on Erlang-Ejabberd development and i want to create a module on top of Ejabberd 15.07. So i got some code as example in many posts and some tutorials to start developing my own module. Now i came out with this code to get started.
-module(mod_test)
-behaviour(gen_mod).
-ifndef(LAGER).
-define(LAGER, 1).
-endif.
-define(NS_SEEN, <<"jabber:iq:seen">>).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-export([start/2, stop/1, process_sm_iq/3]).
start(Host, Opt) ->
?INFO_MSG(" mod_test starting ", []),
IQDisc = gen_mod:get_opt(iqdisc, Opt, fun gen_iq_handler:check_type/1, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SEEN, ?MODULE, process_sm_iq, IQDisc),
ok.
stop(Host) ->
?INFO_MSG(" mod_test2 stopping ", []),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SEEN),
ok.
process_sm_iq(_From, _To, #iq{type = get, xmlns = ?NS_SEEN} = IQ) ->
?INFO_MSG(" Processing IQ Get query:~n ~p ", [IQ]),
IQ#iq{type = result, sub_el = [{xmlelement, <<"value">>, [], [{xmlcdata, <<"Hello World of Testing. ">>}]}]}.
So here, using Strophe.js i send a custom IQ stanza with this function.
var iq = $iq( {type: 'get', id : 'id123', from : 'renato#localhost'}).c('query', {xmlns : 'jabber:iq:seen'});
connection.sendIQ(iq, onResult);
And it comes out with a Stanza like this.
<iq type="get" id="id123" from="renato#localhost" xmlns="jabber:client">
<query xmlns="jabber:iq:seen" >
</iq>
Then i get disconnected and get no result from the server as expected. This is what i got in the log file.
2016-05-11 17:47:28.965 [info]<0.486.0>#ejabberd_listener:accept:299 (#Port<0.3939>) Accepted connection 127.0.0.1:49120 -> 127.0.0.1:5280
2016-05-11 17:47:28.966 [info]<0.507.0>#ejabberd_http:init:157 started: {gen_tcp,#Port<0.3939>}
2016-05-11 17:47:29.511 [info]<0.509.0>#ejabberd_c2s:wait_for_sasl_response:932 ({socket_state,ejabberd_http_bind,{http_bind,<0.508.0>, {{127,0,0,1},49120}},ejabberd_http_bind}) Accepted authentication for renato by undefined from 127.0.0.1
2016-05-11 17:47:30.150 [info]<0.509.0>#ejabberd_c2s:wait_for_session:1120 ({socket_state,ejabberd_http_bind,{http_bind,<0.508.0>, {{127,0,0,1},49120}},ejabberd_http_bind}) Opened session for renato#localhost/32685205291462981649937971
2016-05-11 17:47:31.451 [info]<0.486.0>#ejabberd_listener:accept:299 (#Port<0.3948>) Accepted connection 127.0.0.1:49122 -> 127.0.0.1:5280
2016-05-11 17:47:31.452 [info]<0.511.0>#ejabberd_http:init:157 started: {gen_tcp,#Port<0.3948>}
2016-05-11 17:47:31.453 [info]<0.390.0>#mod_test2:process_sm_iq:45 Processing IQ Get query:
{iq,<<"id123">>,get,<<"jabber:iq:seen">>,<<>>,{xmlel,<<"query">>, [{<<"xmlns">>,<<"jabber:iq:seen">>},{<<"querytype">>,<<"seen">>}],[]}}
2016-05-11 17:49:01.556 [info]<0.508.0>#ejabberd_http_bind:handle_info:522 Session timeout. Closing the HTTP bind session: <<"1199a026fb06de7e7c728425587b09a0b9c81433">>
2016-05-11 17:49:01.556 [info]<0.509.0>#ejabberd_c2s:terminate:1842 ({socket_state,ejabberd_http_bind,{http_bind,<0.508.0>, {{127,0,0,1},49120}},ejabberd_http_bind}) Close session for renato#localhost/32685205291462981649937971
I'm running ejabberd 15.07. I installed the run package and compiled the module using these commands.
erlc -DNO_EXT_LIB -I lib/ejabberd-15.07/include -pz lib/ejabberd-15.07/lib mod_test.erl
After that, i copied the file to ebin/ directory then i started the ejabberd server and everything else worked fine. Someone can help me out and explain me what i have to, and show me if i have to change the code, configuration file or maybe use a newer version. I'm using this version for months. Thanks...
I am using the embedded YAWS web-server with yaws cookie session.
I first authenticate the user with user-name & password to allow him the entry to the web pages.
My problem is if the user directly opens the internal web page instead of login page he can view it even without the authentication. How to restrict the user that he must have the cookie to view any internal web page.
In chapter 7 of the Yaws PDF documentation there's an example that does exactly what you're asking about. It uses arg rewriting to redirect unauthenticated requests to a login page.
First we configure an arg rewriter module named myapp in the server portion of yaws.conf:
arg_rewrite_mod = myapp
The myapp:arg_rewrite/1 function checks the incoming request via the #arg{} record to look for a specific cookie, and if not found and the request isn't trying to retrieve one of the three resources returned from the login_pages/0 function, it calls do_rewrite/1 to rewrite the request to deliver a login.yaws page instead:
arg_rewrite(Arg) ->
OurCookieName = "myapp_sid"
case check_cookie(Arg, OurCookieName) of
{error, _} ->
do_rewrite(Arg);
{ok, _Session} ->
%% return Arg untouched
Arg
end.
%% these pages must be shippable without a good cookie
login_pages() ->
["/banner.gif", "/login.yaws", "/post_login.yaws"].
do_rewrite(Arg) ->
Req = Arg#arg.req,
{abs_path, Path} = Req#http_request.path,
case lists:member(Path, login_pages()) of
true ->
Arg;
false ->
Arg#arg{req = Req#http_request{path = {abs_path, "/login.yaws"}},
state = Path}
end.
Please see the Yaws PDF documentation for further details.
Quoting from the WebSharper 2.5 alpah docs the remoting component assumes that:
RPC-callable methods are safe to call from the web by an unauthenticated client.
Is there anyway to secure remote calls so they can only be called from an authenticated client?
One of the samples in the WebSharper website is a chat application that seems to do just that by providing a Login method that returns an authentication token, which is then required to call the other functions:
[<Rpc>]
let Login (user: string) : Option<Auth.Token> =
let s = State.Get()
if s.Users.ContainsKey user then
None
else
// (snip)
user |> Auth.Generate |> Some
[<Rpc>]
let Poll (auth: Auth.Token) (time: int) =
// (snip)
The full chat sample can be found here: http://www.websharper.com/samples/Chat
Just been playing with this myself. Turns out if you're using Forms Authentication you can read the current HTTPContext from inside RPC methods so you can do something like this:
[<Rpc>]
let protectedMethod () =
match IntelliFactory.WebSharper.Sitelets.UserSession.GetLoggedInUser() with
| Some(username) ->
// User is authenticated... do stuff
()
| None -> failwith "Authentication failed"
I have a working Ejabberd server (version 2.1.9) and my client application running just fine, but I wish to modify the way the application's XMPP client connects to Ejabberd in order to reduce the number of requests/responses between them, because its for a mobile environment and I wish to reduce the initial connection time.
I've looked up the XMPP protocol specification (RFC 6120) and some protocol extensions (XEPs), namely XEP-0305 Quickstart, but the protocol itself doesn't specify single request sign in and the Quickstart extension although aims to reduce the number of requests isn't enough for the time reduction I'm looking for.
After searching and not finding any solution I've started to modify both client and server and wish to accomplish the following for now as a proof of concept:
//Client Request
<?xml version='1.0'?>
<stream:stream ... user='user' pass='pass'>
//Server Response
<?xml version='1.0'?>
<stream:stream ... success='1'>
I've managed to modify my client accordingly and the Ejabberd server, and it seems they connect successfully, but any request the client makes after establishing the session doesn't get a response by the server. I've used Wireshark to check the TCP connection client and server side: client side its open and the request is sent, and on the server side is also open and the request is received, but when I try to send the response it is not sent.
I've modified ONLY the file ejabberd_c2s.erl and the changes are the following:
//init function
...
%% changed the first state of the fsm to point to quickstart
%% {ok, wait_for_stream, #state{socket = Socket1,
{ok, wait_for_quickstart, #state{socket = Socket1,
...
//wait_for_quickstart function
...
case resource_conflict_action(U, StateData#state.server, R) of
closenew ->
send_header(StateData, Server, "1.0", DefaultLang, "0"),
send_trailer(StateData),
{stop, normal, StateData};
{accept_resource, R2} ->
JID = jlib:make_jid(U, StateData#state.server, R2),
allow = acl:match_rule(Server,c2s,JID),
case ejabberd_auth:check_password(U, Server, P) of
true ->
send_header(StateData, Server, "1.0", DefaultLang, "1"),
change_shaper(StateData, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
StateData#state.server,
{[], []},
[U, StateData#state.server]),
LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
Fs1 = [LJID | Fs],
Ts1 = [LJID | Ts],
PrivList =
ejabberd_hooks:run_fold(
privacy_get_user_list,
StateData#state.server,
#userlist{},
[U, StateData#state.server]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip},
{conn, Conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:open_session(SID, U, StateData#state.server, R, Info),
NewStateData =
StateData#state{
user = U,
resource = R2,
jid = JID,
sid = SID,
conn = Conn,
auth_module = ejabberd_auth_internal,
authenticated = true,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList},
fsm_next_state_pack(session_established,
NewStateData);
_ ->
%%auth fail
end
end.
Just to clarify: the initial client authentication request and server response are being transmitted just fine, subsequent requests are also being transmitted but there is no response to them.
I'm I overlooking something?
Thanks in advance
#Nuno-Freitas Indeed that was what was failing, thanks for your helpful insight.
I added the code:
R1 = xml:get_attr_s("r",Attrs),
R = case jlib:resourceprep(R1) of
error -> error;
"" ->
lists:concat([randoms:get_string() | tuple_to_list(now())]);
Resource -> Resource
end,
That made the server respond to my requests, but there was other thing amiss: the <presence/> tag was breaking on server because the #state.lang was not defined, so I had to define it in the wait_for_quickstart function and now I have a single sign in XMPP client server working proof of concept.