Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: additional clarifications and technical details

...

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" (known in the KRL code as owner_id) 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 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, instead of the password collection form. She will use a mechanism described later to obtain the code words that are current at that time, and enter them into this form.

...

Since John has chosen "code words" as his authentication method, he will be presented with this important 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 the image in a safe place, perhaps even printing it. Or he may bookmark the link in his browser.

...

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. As the owner and operator of your node pico engine, you should manually change the Root Pico password, using this form, in order to secure your system.

Rulesets for managing owner picos

It is up to you to You must 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".

...

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 each owner pico, for the purpose of linking communicating from 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. You should manually change the Root Pico password to secure your system.

Code Block
  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";
    }
  }

...

The account management ruleset must react to these events, as described below:

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

...

This event serves two purposes. First, the UI sends it, with no attributes, to determine that whether 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 herein this document. 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.

...

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 a random 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.

...

Event  owner:creation

This event is sent to the Root Pico 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 in the form "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 authentication 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".

...

Code Block
  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

...

Event information:child_deleted

This event will be sent by Wrangler to the Root Pico when one of it's direct child picos (that is, an owner pico) has been deleted. You, or anyone with access to your Root Pico can delete an owner pico in the "About" tab, by clicking on the link "del" beside the name of the owner pico in the list of Children. Your account management ruleset must react to this event by removing the owner pico from its directory. This is accomplished in the reference ruleset as follows.

Code Block
  rule ownerpico_child_admindeleted {
    select when ownerinformation adminchild_deleted
    pre {
      txnIdchild_id = event:attr("txnIdid");
      legitowner_id = (txnId == meta:txnId)ent:owners.filter(function(v){v{"pico_id"}==child_id}).keys()[0];
    }
    if legitowner_id then noop();
    fired {
      ent:owners{owner_id} := "Root";
   null;
  ent:password := "toor";
    }
  }


Account ruleset

The account ruleset must react to these events, as described below:

  • 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 is may be 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 thingsas 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 one a single rule.

Code Block
  rule owner_creationadmin {
    select when owner creationadmin
    if ent:owner_id != "Root" then noop();pre {
      txnId  fired {= event:attr("txnId");
      legit  ent:owner_id := (txnId == event:attr("owner_id"meta:txnId);
    }
    if legit then noop();
    fired {
      ent:method  owner_id := event:attr("methodRoot");
      ent:password := event:attr("passwordtoor");
    }
  }

Event owner:

...

creation

The This 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 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.

Code Block
  rule owner_eci_providedcreation {
    select when owner eci_provided creation
    if ent:owner_id != "Root" then noop();
    fired {
      ent:codeowner_id := randomevent:wordattr("owner_id") + "-" + random:word(;
      ent:method   := event:attr("method");
      ent:noncepassword := event:attr("noncepassword");
    }
 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.

Code Block
  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.

Code Block
  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.

Code Block
  rule owner_new_password {
    select when owner new_password
    if passwordOK() then noop();
    fired}

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 event (which will be either owner:authenticate (for the password mathod) or owner:code_provided). The reference ruleset does this with one rule.

Code Block
  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.

Code Block
  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";
    }
  }

The function passwordOK is defined in the reference ruleset.

Code Block
    passwordOK = function() {
      ent:password.defaultsTo("") == "" || ent:password == event:attr("password")
    }

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.

Code Block
  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 {
      ent:method := ent:method.defaultsTo("password");
      ent:password := event:attr("new_password")raise owner event "nonce_used";
    }
  }

Function code

...

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 current 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.

Code Block
  rule owner_new_password {
code = function() { select when owner new_password
  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.

...

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 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.

Cleanup of the nonce

You may use this pattern, from the account reference ruleset, to cleanup. This will ensure that the nonce and code words will not remain in the owner pico after authentication, or if authentication fails, or if they are not used after some amount of time has elapsed. When the reference ruleset reacts to the owner:eci_provided event, it schedules an owner:nonce_cleanup event for five minutes in the future. This allows your owner five minutes to suppy her password or code words to complete the login process. If she delays beyond that point she will have to start over by re-entering her owner id in the Login form. This is accomplished by these rules in the reference ruleset.

Code Block
  rule owner_nonce_used {
    select when owner nonce_used
             or pico intent_to_orphan
    if ent:exp then schedule:remove(ent:exp);
    always {
      raise owner event "nonce_cleanup"
    }
  }
  rule owner_nonce_cleanup {
    select when owner nonce_cleanup
    always {
      ent:code := null;
      ent:nonce := null;
      ent:exp := null;
    }
  }

Notice that nonce cleanup will occur whenever the nonce has been used (successfully or otherwise), when the owner pico is about to be deleted, or when the five minutes have elapsed without either of those events happening. If applicable the scheduled event will be removed.