Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 4 Next »

After you have installed your node pico engine (see Pico Engine Quickstart for instructions), you begin to operate it. Anyone who can access the URL of your engine has full access to all of the picos hosted on your engine.

You may wish to require a password-protected login. This functionality is built-in to the engine, starting at version 0.12.9, and is described herein.

Principles of operation

With account management, each immediate child pico of the Root Pico is treated as an Owner Pico. The name of each Owner Pico becomes the "owner identifier" and is associated with an authentication method. A directory of owner picos is maintained by a ruleset which is installed in the Root Pico. An associated ruleset must be installed in each owner pico.

The developer UI identifies the currently logged-in owner pico by having its pico identifier saved in the JavaScript-provided sessionStorage using the key owner_pico_id.

With an account management ruleset installed in the Root Pico, the UI will require login to access either the Root Pico or any of the registered owner picos. The UI provides a two-step interface for login, with an initial form requesting entry of the owner id, and a second form requesting entry of the associated password.

Login form

Password form

Code words form

The UI provides a second method of authentication, via "code words". If an owner elects to use this method, then when she has entered her owner id, she will be presented with this form. She will use a mechanism described later to obtain the code words and enter them into this form.

Creating an owner pico

A person who wishes to become an owner on your pico engine will click on the link "Need an owner pico?" whereupon he will be presented with this sign-up form.

He will enter the desired name of his owner pico which will also serve as his owner id, and then select an authentication method and, if applicable, an initial password.

Since John has chosen "code words" as his authentication method, he will be presented with this information, which he must save for future logins. He can right-click on the QR code image and select "Save Image As..." and put it in a safe place, perhaps even printing it. Or he may bookmark the link in his browser.

Once having saved the URL which will provide code words, he will click the "Got it" link to complete the creation of his owner pico. Later on, when he logs in, he will visit this URL (perhaps by sanning the QR code) to receive the current code words.

Changing a password

An owner can change her password in the "About" tab of her owner pico. Here we show this form in the context of the Root Pico.

Rulesets for managing owner picos

It is up to you to provide both the account management ruleset for the Root Pico, and an associated account ruleset for the owner picos. We'll show you how these rulesets can be written, and we provide reference rulesets. You may use these rulesets as-is, or you may create your own. To use them as-is, you would go to the "Rulesets" tab of your Root Pico in the UI, enter this URL into the box and click the button "install ruleset from URL".

When a ruleset is installed in the Root Pico, the event pico:ruleset_added is sent to the Root Pico. This event may be handled by your account management ruleset (if not, you will need to initialize things in some other way). The reference ruleset reacts to this event in the way shown here.

  rule owner_initialize {
    select when pico ruleset_added
    if not ent:owners then noop();
    fired {
      ent:owners := {};
    }
  }

  rule pico_ruleset_added {
    select when pico ruleset_added rid re#temp_acct_mgr#
    every {
      engine:installRuleset(url="temp_acct.krl", base=meta:rulesetURI);
      engine:newChannel(name=time:now(),type="to owner") setting(new_channel);
    }
    fired {
      raise owner event "admin" attributes { "txnId": meta:txnId };
      ent:owners{"Root"} :=
        { "pico_id": meta:picoId,
          "eci": new_channel{"id"},
          "dname": "Root Pico",
          "method": "password"
        };
    }
  }

The reference account management ruleset takes advantage of this event to initialize its entity variable, and your ruleset may do the same. The ruleset must install an account ruleset into the same pico (the Root Pico functions as an Owner Pico), and may send it an event, such as the owner:admin event used in the reference rulesets, to initialize it. If this is done, it should secure the event by including a one-time value, such as the transaction ID as done by the reference rulesets. Finally, the account management ruleset must record the information it will need later to respond to the owner:eci_requested event. Your rulesets should use a new ECI for the purpose of linking the Root Pico to owner picos.

The reference account ruleset reacts to the owner:admin event as follows. The reference rulesets establish the owner ID "Root" and initial password "toor" but your rulesets may make different choices.

  rule owner_admin {
    select when owner admin
    pre {
      txnId = event:attr("txnId");
      legit = (txnId == meta:txnId);
    }
    if legit then noop();
    fired {
      ent:owner_id := "Root";
      ent:password := "toor";
    }
  }

Account Management ruleset

The account management ruleset must react to these events:

  • owner:eci_requested 
  • owner:creation
  • information:child_deleted

Event owner:eci_requested

This event serves two purposes. First, the UI sends it, with no attributes, to determine that there is an account management ruleset (if not, then the login feature is disabled, and everyone has access to all picos). The rule in this case must return a directive with at least a name. The directive itself is used by the UI to set options, and its presence signals that you do require owner login, and promises that your rulesets meet the other requirements described here. The available options are: immediateLogin which you may set to true if you prefer that the owner of a newly created owner pico be logged in after creation (otherwise the new owner will be required to log in at that point), and rid which you may set to the ruleset identifier of your account managment ruleset. The rid option is not currently used, but may be used by the UI in the future.

This is the rule from the reference ruleset. Although we mention it first here, it must be placed last in the account management ruleset.

  rule owner_pico_options {
    select when owner eci_requested
    send_directive("options",{"immediateLogin":true,"rid":meta:rid});
  }

The second, and primary purpose of this event is that the UI causes the engine to send it to the Root Pico whenever someone presses the "Login" button from the Login form. This event will have a single attribute, owner_id, which is what the person entered into the Login form. Your ruleset must return a directive, whose name is ignored but should be the same whether the owner_id is known or not. If the owner_id is known, the directive options must include pico_id, ecinonce, and method. The directive options may include owner_id. If successful, your ruleset must send the event owner:eci_provided to the owner pico, which must include as attributes the nonce, and may include other items.

These two rules from the reference account management ruleset illustrate these requirements. Notice that these rules need to appear before the option-providing rule, and that they include the last statement in their fired clauses, so that only one of these three rules will fire when the owner:eci_requested event is recieved. These rules use an entity variable, ent:owners, to keep track of the owner picos. Your ruleset may also use this entity variable, but must somehow keep track of the owner picos, and may record other information, such as the last login attempt information, as stored here in the entity variable, ent:lastOne.

  rule find_owner_pico_by_name {
    select when owner eci_requested
    pre {
      owner_id = event:attr("owner_id");
      entry = ent:owners{owner_id};
      pico_id = entry{"pico_id"};
      eci = entry{"eci"};
      method = entry{"method"};
      nonce = random:word();
      options = {"owner_id":owner_id,"pico_id":pico_id,"eci":eci,"method":method,"nonce":nonce};
    }
    if eci then every {
      send_directive("here it is",options);
      event:send({"eci":eci, "domain":"owner", "type":"eci_provided", "attrs":options});
    }
    fired {
      last;
      ent:lastOne := options;
    }
  }

  rule owner_pico_not_found {
    select when owner eci_requested
    if event:attr("owner_id") then
    send_directive("here it is",{"owner_id":event:attr("owner_id"),"method":"password"});
    fired {
      last;
    }
  }

Event  owner:creation

This event is sent by the engine, at the request of the UI, when a new owner clicks the "Submit" button on the account creation form. It includes the attributes owner_id (labelled "Pico name") and method. If the method is "password", then it also includes an attribute for the owner-supplied password.

Your account management ruleset must record this information in an entity variable. It must check for uniqueness of the owner_id. Your ruleset must create a new owner pico as a direct child of the Root Pico. Upon successful creation, your ruleset must send a directive whose name may be "new owner pico" (but must not be "new owner pico code query channel") with options which must include pico_id and eci, and may include other values. Finally, if the owner selected the method "code words", your ruleset must send a directive whose name must be "new owner pico code query channel" with options which must include eci and may include other values. The eci provided should be one specially created for the purpose of providing the current code words to the user, as described below in the section entitled "Function code".

The account management reference ruleset fulfills these requirements as follows.

  rule owner_creation_owner_id_uniqueness_guard {
    select when owner creation
    pre {
      owner_id = event:attr("owner_id");
    }
    if ent:owners >< owner_id then
      send_directive("owner_id already in use",{"owner_id": owner_id});
    fired {
      last;
    }
  }

  rule owner_creation {
    select when owner creation
    fired {
      raise pico event "new_child_request" attributes event:attrs();
    }
  }

  rule pico_new_child_created {
    select when pico new_child_created
    pre {
      child_id = event:attr("id");
      child_eci = event:attr("eci");
      rs_attrs = event:attr("rs_attrs");
      owner_id = rs_attrs{"owner_id"};
      method = rs_attrs{"method"};
      code = method == "code";
      dname = rs_attrs{"dname"} || owner_id;
    }
    every {
      engine:installRuleset(child_id, url="temp_acct.krl", base=meta:rulesetURI);
      event:send({"eci":child_eci, "domain":"owner", "type":"creation", "attrs":rs_attrs});
      engine:newChannel(child_id,time:now(),"to owner") setting(new_channel);
      send_directive(
        "new owner pico",
        { "owner_id": owner_id,
          "pico_id": child_id,
          "eci": new_channel{"id"},
          "method": method});
    }
    always {
      raise owner event "new_owner_pico_with_code"
        attributes rs_attrs.put({"owner_pico_id":child_id}) if code;
      ent:owners{owner_id} := 
        { "pico_id": child_id,
          "eci": new_channel{"id"},
          "dname": dname,
          "method": method
        };
    }
  }

  rule owner_new_owner_pico_with_code {
    select when owner new_owner_pico_with_code
    pre {
      owner_pico_id = event:attr("owner_pico_id");
    }
    every {
      engine:newChannel(owner_pico_id,"code query","secret") setting(code_query_channel);
      send_directive("new owner pico code query channel",
        event:attrs().put("eci",code_query_channel{"id"}));
    }
  }


Account ruleset

The account ruleset must react to these events:

  • owner:admin
  • owner:creation
  • owner:eci_provided
  • owner:authenticate
  • owner:code_presented
  • owner:new_password

In addition, it must share this function:

  • code

Event owner:admin

This event may be sent by your account management ruleset as part its initialization. It establishes an initial password for the Root Pico, which you should manually change to secure your pico engine. The reference ruleset does this with a single rule.

  rule owner_admin {
    select when owner admin
    pre {
      txnId = event:attr("txnId");
      legit = (txnId == meta:txnId);
    }
    if legit then noop();
    fired {
      ent:owner_id := "Root";
      ent:password := "toor";
    }
  }

Event owner:creation

This event is sent by your account management ruleset after it has created the new owner pico and installed in it your account ruleset. It must record the owner's initial password (if that is the authentication method chosen by the owner), and may do other things. The reference ruleset does this with one rule.

  rule owner_creation {
    select when owner creation
    if ent:owner_id != "Root" then noop();
    fired {
      ent:owner_id := event:attr("owner_id");
      ent:method   := event:attr("method");
      ent:password := event:attr("password");
    }
  }

Event owner:eci_provided

The event is sent by your account management ruleset after it has provided the UI with the ECI to be used to contact the (as yet unauthenticated) owner pico. Your ruleset must establish the current code words, if this owner uses that method. It should record the nonce used to securely connect the authentication form to the next ruleset. The reference ruleset does this with one rule.

  rule owner_eci_provided {
    select when owner eci_provided
    fired {
      ent:code := random:word() + "-" + random:word();
      ent:nonce := event:attr("nonce");
      schedule owner event "nonce_cleanup" at time:add(time:now(), {"minutes": 5}) setting(exp);
      ent:exp := exp;
    }
  }

Your ruleset may provide for clean-up by scheduling an event as shown.

Event owner:authenticate

This event is sent by the UI when the owner pushes the "Login" button on the password entry form, and has as attributes the nonce and the owner-supplied password. Your account ruleset should verify that the nonce attribute matches the saved value recorded when it reacted to the owner:eci_provided event. Your ruleset must send a directive, with a name of your choice, whose options must include pico_id and eci and may include other values. If the password or nonce are incorrect, your ruleset must not send a directive. The reference ruleset does this with one rule.

  rule owner_authenticate {
    select when owner authenticate
    if event:attr("nonce") == ent:nonce && passwordOK()
    then send_directive("success",{"pico_id":meta:picoId,"eci":meta:eci});
    always {
      raise owner event "nonce_used";
    }
  }

Event owner:code_presented

This event is sent by the UI when the owner pushes the "Login" button on the code words entry form, and has as attributes the nonce and the owner-supplied code words (code). Your account ruleset should verify that the nonce attribute matches the saved value recorded when it reacted to the owner:eci_provided event. If the code words are correct, your ruleset must send a directive, with a name of your choice, whose options must include pico_id and eci and may include other values. If the code words or nonce are incorrect, your ruleset must not send a directive. The reference ruleset does this with one rule.

  rule owner_match_code {
    select when owner code_presented
    if event:attr("code") == ent:code && event:attr("nonce") == ent:nonce then
      send_directive("success",{"pico_id":meta:picoId,"eci":meta:eci});
    always {
      raise owner event "nonce_used";
    }
  }

Event owner:new_password

This event is sent by the UI when the owner pushes the "New Password" button on the Change Password entry form in the owner pico's "About" tab, and has as attributes the password and the owner-supplied new_password. Your account ruleset should verify the current password. Your ruleset must record the new password. The reference ruleset does this with one rule.

  rule owner_new_password {
    select when owner new_password
    if passwordOK() then noop();
    fired {
      ent:method := ent:method.defaultsTo("password");
      ent:password := event:attr("new_password");
    }
  }

Function code

Your account ruleset must be able to provide the current code words (established when it reacted to the owner:eci_provided event) when requested by the owner, who uses the URL provided when he created his owner pico. The reference ruleset defines this function, and which it must also mention in the shares clause of its meta block.

    code = function() {
      ent:code || "code words expired"
    }

Technical notes

Still to be done: as soon as a suitable one-way hash is available in the engine, this must be used so that we never store the password as plain text, but always encrypted with a one-way hash.

Besides the reference rulesets shown on this page, there are official rulesets, named io.picolabs.account_management and io.picolabs.owner_authentication, located in GitHub. Because of their location, they will already be registered in your node pico engine. You may choose to use these rulesets instead of creating your own. You may find it instructive to compare them with the reference rulesets, which they will eventually replace.

Throughout this documentation page, we have used "must, must not, may, and should" in the same sense in which they are used in IETF documents, as explained in RFC2119.



  • No labels