Securing WebSharper remote calls - f#

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"

Related

How to send a speech to text request using google_speech1 in Rust?

I am trying to use google_speech1 for Rust, but the documentation provides incomplete examples, which makes it very hard for me, being both new at Rust and at using Google Speech Api, to figure out how to do send a speech to text request.
More specifically, I would like to be able to send a local audio file, indicate the source language and retrieve the transcription.
Here is the closest I could find in the official documentation(https://docs.rs/google-speech1/1.0.8+20181005/google_speech1/struct.SpeechRecognizeCall.html):
use speech1::RecognizeRequest;
// As the method needs a request, you would usually fill it with the desired information
// into the respective structure. Some of the parts shown here might not be applicable !
// Values shown here are possibly random and not representative !
let mut req = RecognizeRequest::default();
// You can configure optional parameters by calling the respective setters at will, and
// execute the final call using `doit()`.
// Values shown here are possibly random and not representative !
let result = hub.speech().recognize(req)
.doit();
UPDATE
Taking a step back, even simple examples provided on the website don't seem to run properly. Here is some sample very basic code:
pub mod speech_api_demo {
extern crate google_speech1 as speech1;
extern crate hyper;
extern crate hyper_rustls;
extern crate yup_oauth2 as oauth2;
use oauth2::{ApplicationSecret, Authenticator, DefaultAuthenticatorDelegate, MemoryStorage};
use speech1::Speech;
use speech1::{Error, Result};
use std::fs::File;
use std::io::Read;
#[derive(Deserialize, Serialize, Default)]
pub struct ConsoleApplicationSecret {
pub web: Option<ApplicationSecret>,
pub installed: Option<ApplicationSecret>,
}
pub fn speech_sample_demo() {
/*
Custom code to generate application secret
*/
let mut file =
File::open("C:\\Users\\YOURNAME\\.google-service-cli\\speech1-secret.json").unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
use serde_json as json;
let my_console_secret = json::from_str::<ConsoleApplicationSecret>(&data);
assert!(my_console_secret.is_ok());
let unwrappedConsoleSecret = my_console_secret.unwrap();
assert!(unwrappedConsoleSecret.installed.is_some() && unwrappedConsoleSecret.web.is_none());
let secret: ApplicationSecret = unwrappedConsoleSecret.installed.unwrap();
/*
Custom code to generate application secret - END
*/
// Instantiate the authenticator. It will choose a suitable authentication flow for you,
// unless you replace `None` with the desired Flow.
// Provide your own `AuthenticatorDelegate` to adjust the way it operates and get feedback about
// what's going on. You probably want to bring in your own `TokenStorage` to persist tokens and
// retrieve them from storage.
let auth = Authenticator::new(
&secret,
DefaultAuthenticatorDelegate,
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
<MemoryStorage as Default>::default(),
None,
);
let mut hub = Speech::new(
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
auth,
);
let result = hub.operations().get("name").doit();
match result {
Err(e) => match e {
// The Error enum provides details about what exactly happened.
// You can also just use its `Debug`, `Display` or `Error` traits
Error::HttpError(_)
| Error::MissingAPIKey
| Error::MissingToken(_)
| Error::Cancelled
| Error::UploadSizeLimitExceeded(_, _)
| Error::Failure(_)
| Error::BadRequest(_)
| Error::FieldClash(_)
| Error::JsonDecodeError(_, _) => (println!("{}", e)),
},
Ok(res) => println!("Success: {:?}", res),
}
}
}
Running this code (calling speech_sample_demo) gives the following error:
Token retrieval failed with error: Invalid Scope: 'no description
provided'
I also tried some very ugly code to force the scope into the request, but it did not make any difference. I am having a hard time understanding what this error means. Am I missing something in my request or is it something else getting in the way at the other end? Or maybe that api code library is just broken?
Please also note that client id and client secret provided by default don't work anymore, when I was using those it would say that account is deleted.
I then set up an OAuth 2.0 client and generated the json file which I copied over to default location and then started getting the error above. Maybe it is just me not setting Google Api account properly, but in any case would be great if someone else could try it out to see if I am the only one having those issues.
Once I get over running such a simple request, I have some more code ready to be tested that sends over an audio file, but for now it fails very early on in the process.
The error you get originates from here and means that the OAuth scope you used when generating your credentials file doesn't allow you to access the Google speech API. So the problem is not in your Rust code, but instead in the script you used to generate your OAuth access tokens.
Basically, this means that when you generated your OAuth json file, you requested access to the Google API in a general way, but you didn't say which specific APIs you meant to use. According to this document, you need to request access to the https://www.googleapis.com/auth/cloud-platform scope.
You are missing the flow param to Authenticator. This is how you get the access token. You create an Enum using FlowType.
example:
use oauth2::{ApplicationSecret, Authenticator, DefaultAuthenticatorDelegate, MemoryStorage,FlowType};
let Flo = FlowType::InstalledInteractive;
let auth = Authenticator::new(
&secret,
DefaultAuthenticatorDelegate,
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
<MemoryStorage as Default>::default(),
None,)
See here: https://docs.rs/yup-oauth2/1.0.3/yup_oauth2/enum.FlowType.html
Not exactly easy to figure out.
I made this work via service accounts by doing this
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_only()
.enable_http1()
.build();
let service_account_key: oauth2::ServiceAccountKey = oauth2::read_service_account_key(
&"PATH_TO_SERVICE_ACCOUNT.json".to_string(),
)
.await
.unwrap();
let auth = oauth2::ServiceAccountAuthenticator::builder(service_account_key)
.build()
.await
.unwrap();
let hub = Speech::new(hyper::Client::builder().build(https), auth);

How to beautifully make some API functions available only to authorized users in Erlang?

I have functions in my API code, some of which should only return requested results only to those requests that have a correct token. If the request is not authorized, then return a generic 401 response.
I have created a helper function is_authorised() which returns true/false.
Now, in a function that I want to make accessible only to authorized users, I check the result of the is_authorised() function and return respective response.
Here is an example:
get_players(SessionID, _Env, _Input) ->
case is_authorized(_Env) of
true ->
Response = [json_header(), players:select_all()];
false ->
Response = get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
I wonder if it's possible to make this checking look more elegant, like Python decorators used in Flask.
#app.route('/user')
#required_roles('admin', 'user')
def user_page(self):
return "You've got permission to access this page."
I know I can't use custom functions in guards, so I think it's not possible to make it as a guard.
Please advise.
Not as elegant as Python decorators, but you can use higher order functions to make this much cleaner. Create a function that accepts SessionID, Env, Input, and a callback function that should be executed when the user is authorized, and call that from all the functions that need to do the authorization check.
(Untested) code:
authorized(SessionID, Env, Input, Fun) ->
Response = case is_authorized(Env) of
true ->
Fun();
false ->
get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
get_players(SessionID, Env, Input) ->
authorized(SessionID, Env, Input, fun() ->
[json_header(), players:select_all()]
end).
You can pass more arguments to authorized if you want to do more extensive checks. With a role_of/1 function that accepts Env and returns the role as an atom, you can allow access to certain users with something like this:
authorized(SessionID, Env, Input, Roles, Fun) ->
Response = case lists:member(role_of(Env), Roles) of
true ->
Fun();
false ->
get_unauthorized_response()
end,
mod_esi:deliver(SessionID, Response).
get_players(SessionID, Env, Input) ->
authorized(SessionID, Env, Input, [admin, user], fun() ->
[json_header(), players:select_all()]
end).
You might want to look into cowboy or webmachine as they'd give you a framework over a state machine to handle REST requests. Implement the 'forbidden/2' callback to indicate unauthorized access.
On a side note: a 401 status code indicates a failure of authentication at the http layer when doing one of the well known http auth mechanisms like basic. See also https://en.wikipedia.org/wiki/Basic_access_authentication
What you are dealing with is a authorization failure and the correct associated http status code for that case would be 403 (as the forbidden callback from above would return).

Json Type Provider -> using Load and credentials

I am making the following request which is returning Json.
let baseUrl = "http://wex-qa.mybluemix.net/resources/question"
let userName = "yourName#aol.com"
let password = "yourCreds"
let authKey = userName + ":" + password
let client = new HttpClient()
client.DefaultRequestHeaders.Authorization <- new AuthenticationHeaderValue("Basic",authKey)
let input = new Dictionary<string,string>()
input.Add("question","what time is it")
let content = new FormUrlEncodedContent(input)
let result = client.PostAsync(baseUrl,content).Result
let resultContent = result.Content.ReadAsStringAsync().Result
I immediately thought of using the Json Type Provider so I made a local file of the response to be the type def. I then went to load the type where I need the credentials and the content. However, I did not see where the .Load() function have an overload to pass in the credentials and the content. Should I continue to make the request via the HttpClient class and use the .Parse() function?
Thanks in advance.
Expanding on the answer in the comments (which is correct).
I had this problem in 2022 with FSharp.Data 4.2.9. There still seems to be no way to put authorisation credentials into the requests for either defining the JsonProvider type, or the runtime collection of data. With a bit of working around, it can be made to work like a type provider, as described in the question and comment.
Get your credentials for web requests from the site. This often involves a single 'key' rather than a username/password pair.
Instantiate the type provider with sample data from the site (extracted with the required credentials).
At runtime, use HttpClient with Authorization, to read JSON from the URL, and then use the Parse method of the type provide to return typed data. This allows you to use intellisense on the returned data
This example is for the MailChimp API, which supports mailing lists and requires a company API Key on web requests.
First, follow instructions on the site to get 'Credentials' for API calls.
Second, use any method to extract sample JSON to a file. I used the PHP samples provided by MailChimp.
Then define the JsonProvider with that data.
type mcListsTypeProvider = JsonProvider< "./lists_sample_data.json">
Here lists is a fundamental component of the API. In this example, I will be working with the "Sales Subscribers" list.
The next function will read from a URL, and return typed Json data, as if directly from the Load function.
The core of this is the jsonProviderParse argument, which is the Parse method from the JsonProvider.
let loadTypedJson url jsonProviderParse =
use httpClient = new HttpClient()
// Set up authorisation
let mailChimpApiKey = "3ef8dXXXXXXXXXXXXXXXXf61e-us18" // Could be a "userName:passowrd" key
httpClient.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Basic",mailChimpApiKey)
// Get content
let result = httpClient.GetAsync(url : string).Result
let content = result.Content.ReadAsStringAsync().Result
// Parse content, with the Parse method from the JsonProvider
jsonProviderParse content
Then use loadTypedJson as you would myTypeProvider.Load("https:..")
// Find the MailChimp list Id for the "Sales Subscribers" list
let listsUrl = "https://us18.api.mailchimp.com/3.0/lists"
let data = loadTypedJson listsUrl mcListsTypeProvider.Parse
let list_id =
data.Lists
|> Seq.find (fun list -> list.Name = "Sale Subscribers")
|> (fun x -> x.Id)
printfn "Sale Subscribers list_id = %s" list_id
Output: list_id = f21XXXXX85
Intellisense working for the JsonProvider

User can access internal yaws pages without authentication

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.

Ejabberd Single Request Sign On

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.

Resources