...
We'll show you how these rulesets can be written, and we provide reference rulesets. These two rulesets are referred to in this page as the "reference account management ruleset" and the "reference account ruleset". 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".
...
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"; raise owner event "pwd_needs_encoding" attributes ent:password :={ "password": "toor" }; } } |
Account Management ruleset
...
owner:admin
owner:creation
owner:eci_provided
owner:authenticate
owner:code_presented
owner:new_password
In addition, it must share this function:
code
...
The account ruleset may provide an event to securely store the owner-provided password. In the reference ruleset, this is owner:pwd_needs_encoding
In addition, it must share this function:
code
Event owner:admin
This event may be sent by your account management ruleset as part of 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.
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"; raise owner event "pwd_needs_encoding" attributes { ent:"password ":= "toor" }; } } |
Event owner:creation
...
Code Block |
---|
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"); raise owner event "pwd_needs_encoding" attributes { ent:"password ":= event:attr("password") }; } } |
Event owner:eci_provided
...
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() event:attr("password")) then send_directive("success",{"pico_id":meta:picoId,"eci":meta:eci}); alwaysfired { raise owner event "noncepwd_needs_usedencoding"; attributes { "password": event:attr("password") } } |
The function passwordOK
is defined in the reference ruleset.
Code Block |
---|
passwordOK = functionif pwd_needs_encoding(); { } ent:password.defaultsTo("") == "" || ent:password == event:attr("password") } |
...
finally {
raise owner event "nonce_used";
}
} |
The function passwordOK
is defined in the reference ruleset.
Code Block |
---|
one_way_hash = function(password) {
math:hash("sha256",ent:password{"salt"} + ":" + password)
}
passwordOK = function() {
ent:password{"password"} == one_way_hash(password)
} |
The function pwd_needs_encoding
is used in the reference ruleset to take advantage of a moment when we have in hand a correct owner password. We may wish to re-encrypt the password under certain conditions. In this example, we will encrypt the password if an earlier version of the ruleset had stored the owner-supplied password as a plain string.
Code Block |
---|
pwd_needs_encoding = function() {
ent:password.typeof() == "String"
} |
This allows for graceful upgrading of password storage mechanisms. Flushing this ruleset in your pico engine which had an earlier version will result in the password being encrypted upon the next successful login attempt by each owner.
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 "LoginNew Password" button on the code words entry formthe Change Password entry form in the owner pico's "About" tab, and has as attributes the nonce
current password
and the owner-supplied code words (code)
new_password
. 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 directivethe current password. Your ruleset must record the new password. The reference ruleset does this with one rule.
Code Block |
---|
rule owner_matchnew_codepassword { select when owner codenew_presentedpassword if passwordOK(event:attr("codepassword") == ent:code && event:attr("nonce") == ent:nonce then send_directive("success",{"pico_id":meta:picoId,"eci":meta:eci}noop(); alwaysfired { ent:method := ent:method.defaultsTo("password"); raise owner event "nonce_used""pwd_needs_encoding" attributes { "password": event:attr("new_password") }; } } |
Event owner:
...
pwd_needs_
...
encoding
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 raised by every rule in the reference account ruleset which has access to the owner's password in clear text. Your account ruleset may have a similar rule.
Code Block |
---|
rule owner_pwd_needs_encoding { select when owner pwd_newneeds_encoding password { re#^(.*)$# setting (password) select whenpre owner{ new_password if passwordOK()salt then= nooprandom:uuid(); } fired { ent:password := {}; ent:password{"salt"} := salt; ent:methodpassword{"password"} := ent:method.defaultsTo("password"one_way_hash(password); ent:password{"last_encoding"} := eventtime:attrnow("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
...
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.
...