Access SAFE Browser Authenticator from desktop app

First question would be:

Is it currently feasible to have a desktop application use the SAFE Browser Authenticator?

If not, then I will implement the authenticator in the desktop application.

On the other hand, if it is, then these are my questions:

I am able to browse a site hosted on localhost in the SAFE browser, (localhost://p:[port nr]) and I get the auth request URL which I try to open in browser.
If I’m browsing any other browser, it asks me to open other app, and then SAFE Browser opens, and if browsing in SAFE Browser it goes directly to the path. Or it almost goes to the path…

If I try open the url, the browser adds safe:// and removes the : so that it looks like this:
safe://safe-auth//[auth req encoded]
…and this won’t open the Authenticator.

But if I manually enter safe-auth://[auth req encoded] in SAFE browser then the Authenticator will display that my app is requesting access.

Question 1: What am I doing wrong here?


As mentioned above, if I enter safe-auth://[auth req encoded] manually, I do get to allow the app, and then the browser tries to open an url in this format:

safe-[encoded url]

I get deserialisation error from DecodeIpcMessageAsync with this string though.

Question 2: What am I doing wrong at this point?


The email and web hosting apps do this, no?

Ah, yes they do.

(from web_hosting_manager/src/lib/utils.js)

const parseUrl = (url) => (
  (url.indexOf('safe-auth://') === -1) ? url.replace('safe-auth:', 'safe-auth://') : url
);

(from web_hosting_manager/src/lib/api.js)

sendAuthReq() {
    return safeApp.initializeApp(this.APP_INFO.data, null, { libPath })
      .then((app) => app.auth.genAuthUri(this.APP_INFO.permissions, this.APP_INFO.opt))
      .then((res) => utils.openExternal(res.uri));
  }

It seems also these need to replace safe-auth: with safe-auth://, but when opening here, I do not think the url is turned into safe://safe-auth//[...] as it does for me.
EDIT: I just now managed to find a small mistake so this question can be ticked off (yay).

But, I guess it answers the first question (it is currently feasible) and thus my following questions have a higher chance of being relevant :slight_smile: (which I hoped for.)

2 Likes

Hmmm, I’m not quite sure what steps you are taking.

Are you generating an auth URI string in a locally hosted application and then running it in your system to try and get SAFE Browser’s authenticator to parse it?

Yes, definitely.

A general overview:

  • Desktop app registers itself with system URI registry
  • Desktop app sends it’s information and desired permissions to authenticator
  • Authenticator, on success, calls URI on system, based on the app info that it received, along with auth URI that it generates
  • Auth URI may now be used to connect to network

You can see these steps working in the example email or web hosting manager apps.

This is good timing though because I’m working on a little prototype app that is simply registering itself and generating an auth URI.

Start here: neon-safe-app-example/main.js at master · hunterlester/neon-safe-app-example · GitHub

The first thing the app is doing is registering itself.

Then refer to this library that the app depends on, safe_app_neon/native/src at master · hunterlester/safe_app_neon · GitHub, in order to see what it’s calling.

Will be a little different for you because you’ll be using FFI layer with .Net, whereas I’m working with core Rust libraries.
Same steps though.

EDIT: someone else posted before I finished this reply. Send me your repo and let’s see what’s up.

1 Like

Also note that the example apps are using shell.openExternal which require us to parse the URI so that safe-auth: becomes safe-auth://, however we are switching the apps over to using our native system_uri open function, which you’ll see me using in my example library.

You don’t need to parse the URI with the native open function.

1 Like

Thanks a lot for the reply @hunterlester :slight_smile:
(The repo is linked further down in the text)

I basically use the code from the mobile apps.
There are hardly any modifications to these parts involved here.

I did however change this

inputUrl = inputUrl.Replace(":", "://maidsafe.net/").TrimEnd('=');

to this

inputUrl = inputUrl.Replace(":", "://").TrimEnd('=');

since I could not get the Authenticator to interpret the URL with "://maidsafe.net/" in it, but it did work without it.

So, the exact steps that are taken:

GenerateAppRequestAsync is called, and I get an auth uri returned, which looks like this:

safe-auth://AAAAAEsyjsAAAAAAGwAAAAAAAABvZXR5bmcuYXBwcy5zYWZlLmV2ZW50c3RvcmUBAAAAAAAAAAAPAAAAAAAAAFNBRkUgRXZlbnRTdG9yZQYAAAAAAAAAT2V0eW5nAQEAAAAAAAAADAAAAAAAAABfcHVibGljTmFtZXMBAAAAAAAAAAEAAAA=

I open this url from html in my app ui (like this: <a class="btn btn-success" href="@Model.AuthUri">), and SAFE Browser opens, and there I allow the app.

Btw, I just now managed to find a small mistake so Question 1 can be ticked off (yay).

After allowing app, a popup says "You'll need a new app to open this safe-[encoded stuff]".
(Here is a Question 3: How do I instead get this string back into the application?)

(Coming to Question 2: )
So I copy this string
(because currently I do not know how to get this string back into my application)
and I enter it manually into HandleUrlActivationAsync - just to see it working. But here I get an exception from ffi: Error code: -1, Serialisation error.

1 Like

I’m typing from my phone, otherwise I’d do a little more footwork.

Compile system_uri, https://github.com/maidsafe/system_uri, which exposes just two FFI functions: open and install

Upon execution of your app, call install to register it with your system.

Upon your approval of app auth request in browser, authenticator will then call the URI which will have been registered with your system already, thereby executing your app.

In electron apps there is an open-url event that is triggered when the system executes the app, where you may capture returned data. https://github.com/hunterlester/neon-safe-app-example/blob/master/main.js#L113

You’ll have to find a similar event in which ever app library you are using.

Now, as to why you are receiving a serialisation error upon doing asp of this manually. It’s usually about mismatched libraries.

Find out which version of safe_app you are using and which version the browser is using.

I’m fairly certain that browser is using v0.4.0

2 Likes

Aha. Interesting. So, I need to include system_uri.dll and call install to register the app with the system? I’m not sure what that does… but from what I can see… I have gotten the scheme back (safe-[encoded stuff]) after allowing my app in Authenticator. But there is also something called execPath in appInfo I see.
I do not know how the scheme is used, or how it needs to look for various situations. (I am guessing it’s safe-[encoded stuff] because the browser is supposed to use safe:// ?)
The execPath I guess though, will be the URI that is called, that will launch the application.

So, it looks to me like we are passing in this scheme and the exec URI when calling install (together with the app info).
Then the Authenticator calls this execPath when app is allowed.

Then I am a little bit wiser I think.

This is all very interesting, and great that you were right now working with it :slight_smile:
I cannot find where the SafeMessages app in mobile libraries call install with appInfo. I have missed this part completely.


So, this is what I have done now:

public class NativeBindings : INativeBindings
{
    #region Install

    public void InstallUri(AppInfo info, InstallUriCb callback)
    {
        InstallUriNative(info.Id, info.Vendor, info.Name, info.Exec, info.Icon, info.Schemes, callback.ToHandlePtr(), OnInstallUriCb);
    }

    [DllImport("system_uri.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "install")]
    public static extern void InstallUriNative(
        string bundle, 
        string vendor, 
        string name, 
        string exec, 
        string icon, 
        string schemes, 
        IntPtr userDataPtr, 
        InstallUriCb callback);

    private static void OnInstallUriCb(IntPtr cbPtr, FfiResult result)
    {
        var cb = cbPtr.HandlePtrToType<InstallUriCb>();
        cb(IntPtr.Zero, result);
    }

    #endregion

and it will be used by this:

public static Task<bool> InstallUriAsync(AppInfo appInfo)
        {
            return Task.Run(
              () => {
                  var tcs = new TaskCompletionSource<bool>();
                  
                  InstallUriCb callback = (_, result) => {
                      if (result.ErrorCode != 0)
                      {
                          tcs.SetException(result.ToException());
                          return;
                      }

                      tcs.SetResult(true);
                  };

                  NativeBindings.InstallUri(appInfo, callback);

                  return tcs.Task;
              });
        }

which in turn will be used by this:

    public async Task<bool> InstallUriAsync()
    {
        State.AppId = "oetyng.apps.safe.eventstore";

        var appInfo = new AppInfo
        {
            Id = AppId,
            Name = "SAFE EventStore",
            Vendor = "Oetyng",
            Icon = "some icon",
            Exec = "",
            Schemes = ""
        };

        var installed = await Session.InstallUriAsync(appInfo);
        
        Debug.WriteLine($"Installed: {installed}");
        return installed;
    }

and this I will call, just like you do, before gen_auth_uri.
(However, since the installation is always done before generating auth uri, I put the call within that method instead)
which gives this:

public async Task<string> GenerateAppRequestAsync()
        {
            if (!await InstallUriAsync())
                throw new InvalidOperationException();

            var authReq = new AuthReq
            {
                AppContainer = true,
                AppExchangeInfo = new AppExchangeInfo { Id = AppId, Scope = "", Name = "SAFE EventStore", Vendor = "Oetyng" },
                Containers = new List<ContainerPermissions> { new ContainerPermissions { ContainerName = "_publicNames", Access = { Insert = true } } }
            };

            var encodedReq = await Session.EncodeAuthReqAsync(authReq);
            var formattedReq = UrlFormat.Convert(encodedReq, false);
            Debug.WriteLine($"Encoded Req: {formattedReq}");
            return formattedReq;
        }

As you can see, I still have to figure out what value the Schemes and Exec properties shall have.
My app will already be running, so opening it again seems strange… It is exposing HTTP endpoints though, so I could execute a simple console application which just calls some HTTP endpoint in my app and then shuts down… Seems like an odd way to go about it, but currently only idea I have.


As to what causes the serialization error:

I got it from here: https://github.com/maidsafe/safe_client_libs/tree/alpha-2/safe_app a few days ago. So, that is 0.4.0.

1 Like

OK. So I “solved” (hacked) it according to my initial idea:

I setup a simple console application (and set AppInfo.Exec to be the path to its exe). It extracts the encoded url from argument passed in from Authenticator (it would come as [scheme]:[encodedUrl]), extracts the encodedUrl and does an HTTP call to my already running localhost server with it. (Drawback here is that it is not a cross-platform solution, since the console app is .NET Framework 4.6).

So, HandleUrlActivation worked (i.e. DecodeIpcMessage + AppRegistered).

But then I ran into problems when doing this

// Create Self Permissions
            using (var categorySelfPermSetH = await MDataPermissions.NewAsync())
            {
                await Task.WhenAll(
                    MDataPermissionSet.AllowAsync(categorySelfPermSetH, MDataAction.kInsert),
                    MDataPermissionSet.AllowAsync(categorySelfPermSetH, MDataAction.kUpdate),
                    MDataPermissionSet.AllowAsync(categorySelfPermSetH, MDataAction.kDelete),
                    MDataPermissionSet.AllowAsync(categorySelfPermSetH, MDataAction.kManagePermissions));

this threw an exception:
"Invalid MutableData permission set handle"
so, some investigation to do there.

But well, the problem of the OP is now solved :slight_smile: Thanks alot @hunterlester :slight_smile:

1 Like

This is a fun thread :smiley:

I’ve not yet a handle on how CS projects are structured, so I’m curious to know: Are the mobile implementations relying on an authenticator built into a browser, just as we do on desktop? Or is there a headless authenticator implemented there?

When testing your CS based app, are you testing against mock browser built from master branch?

1 Like

Yeah, it was really great to have someone point in a direction, I could go compile the dll, quickly code up some things according to existing examples (thanks to mobile libraries examples ), and then it works. Good stuff :slight_smile:

Exactly, this is what they do.

I run it against the live alpha-2 network :slight_smile:
(Have not spent my puts yet, and if the puts can be increased on some request, then I’m happy to keep one less framework/thing running).

This topic was automatically closed after 60 days. New replies are no longer allowed.