├── .editorconfig ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── core ├── Cargo.toml └── src │ └── lib.rs ├── flake.lock ├── flake.nix ├── lib ├── Cargo.toml └── src │ └── lib.rs ├── rpc ├── Cargo.toml └── src │ └── lib.rs ├── storage ├── Cargo.toml └── src │ └── lib.rs └── zome ├── Cargo.toml └── src └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | # Rust 12 | [*.rs] 13 | indent_size = 4 14 | 15 | # Configs 16 | [*.{json,yml,ini}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | # Markdown 21 | [*.{md,markdown}] 22 | trim_trailing_whitespace = false 23 | indent_style = tab 24 | indent_size = 4 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Cargo.lock 3 | .cargo/ 4 | /target/ 5 | 6 | # https://github.com/maxlath/backup-github-repo 7 | /repo-backup 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members= [ 4 | "core", 5 | "lib", 6 | "storage", 7 | "zome", 8 | ] 9 | 10 | [workspace.dependencies] 11 | hdi = "=0.3.1" 12 | hdk = "=0.2.1" 13 | serde = "=1.0.171" 14 | holochain = "=0.2.1" 15 | holo_hash = "=0.2.1" 16 | holochain_serialized_bytes = "=0.0.51" 17 | holochain_zome_types = { version = "=0.2.1", default-features = false } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Holochain Open Dev contributors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNA Auth Resolver 2 | 3 | > A simple configuration-based module for inter-network RPC in Holochain hApps. 4 | 5 | 6 | 7 | - [About](#about) 8 | - [Usage](#usage) 9 | - [In the *origin* zome](#in-the-origin-zome) 10 | - [In the *destination* DNA](#in-the-destination-dna) 11 | - [Troubleshooting](#troubleshooting) 12 | - [Building / developing](#building--developing) 13 | - [Built with this module](#built-with-this-module) 14 | - [License](#license) 15 | 16 | 17 | 18 | 19 | 20 | ## About 21 | 22 | *(TLDR; this module replaces what was formerly known as "bridging" in Holochain-Redux.)* 23 | 24 | You have a Holochain application composed of multiple coordinated application 'cells'. These cells want to talk to each other but need some way of assigning capabilities to each other in order to do so. This helper module and related zome provides this functionality. 25 | 26 | It works by providing a layer for mapping pre-known request identifiers onto the actual zome & method names deployed into a DNA. An open-access endpoint living at the pre-known `remote_auth` zome name in the destination DNA is used to request and assign separate capability tokens which can be retrieved and used at runtime to authenticate method calls between apps. 27 | 28 | 29 | 30 | ## Usage 31 | 32 | ### In the *origin* zome 33 | 34 | > (the one where a record has been modified which will trigger an update in some *destination* zome in a remote DNA) 35 | 36 | 1. Include the `hc_zome_dna_auth_resolver_storage` crate in the zome module to ensure that its capability storage `EntryDef` is available: 37 | ```toml 38 | [dependencies] 39 | hc_zome_dna_auth_resolver_storage = {git = "https://github.com/holochain-open-dev/dna-auth-resolver", tag = "X.X.X" package = "hc_zome_dna_auth_resolver_storage"} 40 | ``` 41 | ```rust 42 | use hc_zome_dna_auth_resolver_storage::*; 43 | ``` 44 | 2. Wherever your cross-DNA logic is triggered, import the `_lib` crate and use the helper methods to communicate with the remote zome: 45 | ```toml 46 | [dependencies] 47 | hc_zome_dna_auth_resolver_lib = {git = "https://github.com/holochain-open-dev/dna-auth-resolver", tag = "X.X.X" package = "hc_zome_dna_auth_resolver_lib"} 48 | ``` 49 | ```rust 50 | use hc_zome_dna_auth_resolver_lib::{DNAConnectionAuth, ensure_authed}; 51 | 52 | // define external permission ID to map to in destination zome config 53 | pub const remote_permission_id: &str = "EXTERNAL_PERMISSION_IDENTIFIER"; 54 | 55 | // pull destination DNA hash from somewhere 56 | let to_dna: DnaHash = //... 57 | 58 | // transparently request & retrieve auth data for remote DNA/zome 59 | let auth_data = ensure_authed(to_dna, remote_permission_id)?; 60 | 61 | // use auth data to make request 62 | let DNAConnectionAuth { claim, method } = auth_data; 63 | let resp = hdk::call( 64 | Some(CellId::new(to_dna, claim.grantor().to_owned())), 65 | method.0, method.1, 66 | Some(claim.secret().to_owned()), 67 | payload, 68 | ); 69 | ``` 70 | 71 | ### In the *destination* DNA 72 | 73 | > (the one containing the zome being "driven" by the *origin* DNA/zome) 74 | 75 | 1. Build and include a compiled-to-WASM version of the `hc_zome_dna_auth_resolver` crate, with the name `remote_auth`. **The zome name is important.** 76 | 1. One possible way of doing this is to re-export the crate from this module in your own derived crate: 77 | ```toml 78 | [package] 79 | name = "hc_zome_my_app_auth_resolver" 80 | version = "0.1.0" 81 | edition = "2021" 82 | private = true 83 | 84 | [dependencies] 85 | hc_zome_dna_auth_resolver = {git = "https://github.com/holochain-open-dev/dna-auth-resolver", tag = "X.X.X", package = "hc_zome_dna_auth_resolver"} 86 | 87 | [lib] 88 | path = "src/lib.rs" 89 | crate-type = ["cdylib", "rlib"] 90 | ``` 91 | ```rust 92 | extern crate hc_zome_dna_auth_resolver; 93 | ``` 94 | 2. Do the same for the `hc_zome_dna_auth_resolver_integrity` crate/zome: 95 | ```toml 96 | [package] 97 | name = "hc_zome_my_app_auth_resolver_integrity" 98 | version = "0.1.0" 99 | edition = "2021" 100 | private = true 101 | 102 | [dependencies] 103 | hc_zome_dna_auth_resolver_integrity = {git = "https://github.com/holochain-open-dev/dna-auth-resolver", tag = "X.X.X", package = "hc_zome_dna_auth_resolver_integrity"} 104 | 105 | [lib] 106 | path = "src/lib.rs" 107 | crate-type = ["cdylib", "rlib"] 108 | ``` 109 | ```rust 110 | extern crate hc_zome_dna_auth_resolver_integrity; 111 | ``` 112 | 3. Include the built zome artifacts in your DNA bundle, along with the destination zomes. 113 | ```yaml 114 | # ... 115 | integrity: 116 | # ... 117 | zomes: 118 | - name: remote_auth_integrity 119 | bundled: ../../target/wasm32-unknown-unknown/release/hc_zome_my_app_auth_resolver_integrity.wasm 120 | coordinator: 121 | zomes: 122 | # ... 123 | - name: remote_auth 124 | bundled: ../../target/wasm32-unknown-unknown/release/hc_zome_my_app_auth_resolver.wasm 125 | dependencies: 126 | - name: remote_auth_integrity 127 | ``` 128 | 2. Add this configuration block to the DNA properties: 129 | ```yaml 130 | properties: 131 | # ... 132 | remote_auth: 133 | permissions: 134 | - extern_id: EXTERNAL_PERMISSION_IDENTIFIER 135 | allowed_method: [TARGET_ZOME_NAME, TARGET_FUNC_NAME] 136 | ``` 137 | 138 | 139 | ### Troubleshooting 140 | 141 | **Errors relating to missing `EntryDef` for `"dna_authed_method_mapping"`:** 142 | 143 | This can happen for zomes which define the `entry_defs` extern themselves rather than using convenience macros; in which case any entry types defined with `#[hdk_entry]` are overridden by the returned array. 144 | 145 | In such cases, you can add this `EntryDef` to your `EntryDefsCallbackResult`: 146 | 147 | ```rust 148 | EntryDef { 149 | id: CAP_STORAGE_ENTRY_DEF_ID.into(), 150 | visibility: EntryVisibility::Private, 151 | required_validations: 1.into(), 152 | required_validation_type: RequiredValidationType::default(), 153 | }, 154 | ``` 155 | 156 | 157 | 158 | ## Building / developing 159 | 160 | Written in [Rust](https://www.rust-lang.org/). Uses regular Cargo manifests & package commands, best included as dependencies in your other packages. 161 | 162 | To reference these crates directly from Github, you can use (eg.) 163 | 164 | hc_zome_dna_auth_resolver_lib = {git = "https://github.com/holochain-open-dev/dna-auth-resolver", tag = "X.X.X" package = "hc_zome_dna_auth_resolver_lib"} 165 | 166 | 167 | 168 | ## Built with this module 169 | 170 | [`hdk_records`](https://github.com/h-rea/hrea/tree/feature/sprout/lib/hdk_records) is a high-level record and index management library for highly modular Holochain apps. 171 | 172 | 173 | 174 | ## License 175 | 176 | Apache-2.0 177 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hc_zome_dna_auth_resolver_core" 3 | version = "0.2.0" 4 | authors = ["pospi "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { workspace = true } 9 | hdi = { workspace = true } 10 | 11 | [lib] 12 | path = "src/lib.rs" 13 | crate-type = ["cdylib", "rlib"] 14 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * DNA Auth Resolver core library 3 | * 4 | * These shouldn't be touched, as much as possible, as they are so used in the integrity zomes. 5 | * They are separate from the integrity zome so they can be safely imported into coordinator zomes 6 | * without triggering conflicting holochain callbacks to be defined 7 | * 8 | * @package @holochain-open-dev/dna-auth-resolver 9 | * @since 2022-07-21 10 | */ 11 | use hdi::prelude::*; 12 | 13 | /// Mapping of externally-facing permission IDs to zome/method call parameters. 14 | /// 15 | /// Used in DNA properties of receiving DNA, stored as an Entry for lookup in 16 | /// the requesting DNA. 17 | /// 18 | #[hdk_entry_helper] 19 | #[derive(Clone, PartialEq)] 20 | pub struct AvailableCapability { 21 | pub extern_id: String, 22 | pub allowed_method: GrantedFunction, 23 | } 24 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "cargo-chef": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1672901199, 7 | "narHash": "sha256-MHTuR4aQ1rQaBKx1vWDy2wbvcT0ZAzpkVB2zylSC+k0=", 8 | "owner": "LukeMathWalker", 9 | "repo": "cargo-chef", 10 | "rev": "5c9f11578a2e0783cce27666737d50f84510b8b5", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "LukeMathWalker", 15 | "ref": "main", 16 | "repo": "cargo-chef", 17 | "type": "github" 18 | } 19 | }, 20 | "cargo-chef_2": { 21 | "flake": false, 22 | "locked": { 23 | "lastModified": 1672901199, 24 | "narHash": "sha256-MHTuR4aQ1rQaBKx1vWDy2wbvcT0ZAzpkVB2zylSC+k0=", 25 | "owner": "LukeMathWalker", 26 | "repo": "cargo-chef", 27 | "rev": "5c9f11578a2e0783cce27666737d50f84510b8b5", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "LukeMathWalker", 32 | "ref": "main", 33 | "repo": "cargo-chef", 34 | "type": "github" 35 | } 36 | }, 37 | "cargo-rdme": { 38 | "flake": false, 39 | "locked": { 40 | "lastModified": 1675118998, 41 | "narHash": "sha256-lrYWqu3h88fr8gG3Yo5GbFGYaq5/1Os7UtM+Af0Bg4k=", 42 | "owner": "orium", 43 | "repo": "cargo-rdme", 44 | "rev": "f9dbb6bccc078f4869f45ae270a2890ac9a75877", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "orium", 49 | "ref": "v1.1.0", 50 | "repo": "cargo-rdme", 51 | "type": "github" 52 | } 53 | }, 54 | "cargo-rdme_2": { 55 | "flake": false, 56 | "locked": { 57 | "lastModified": 1675118998, 58 | "narHash": "sha256-lrYWqu3h88fr8gG3Yo5GbFGYaq5/1Os7UtM+Af0Bg4k=", 59 | "owner": "orium", 60 | "repo": "cargo-rdme", 61 | "rev": "f9dbb6bccc078f4869f45ae270a2890ac9a75877", 62 | "type": "github" 63 | }, 64 | "original": { 65 | "owner": "orium", 66 | "ref": "v1.1.0", 67 | "repo": "cargo-rdme", 68 | "type": "github" 69 | } 70 | }, 71 | "crane": { 72 | "inputs": { 73 | "flake-compat": "flake-compat", 74 | "flake-utils": "flake-utils", 75 | "nixpkgs": [ 76 | "holochain-dev", 77 | "nixpkgs" 78 | ], 79 | "rust-overlay": "rust-overlay" 80 | }, 81 | "locked": { 82 | "lastModified": 1675475924, 83 | "narHash": "sha256-KWdfV9a6+zG6Ij/7PZYLnomjBZZUu8gdRy+hfjGrrJQ=", 84 | "owner": "ipetkov", 85 | "repo": "crane", 86 | "rev": "1bde9c762ebf26de9f8ecf502357c92105bc4577", 87 | "type": "github" 88 | }, 89 | "original": { 90 | "owner": "ipetkov", 91 | "repo": "crane", 92 | "type": "github" 93 | } 94 | }, 95 | "crane_2": { 96 | "inputs": { 97 | "flake-compat": "flake-compat_3", 98 | "flake-utils": "flake-utils_2", 99 | "nixpkgs": [ 100 | "holochain-dev", 101 | "holochain", 102 | "nixpkgs" 103 | ], 104 | "rust-overlay": "rust-overlay_2" 105 | }, 106 | "locked": { 107 | "lastModified": 1675475924, 108 | "narHash": "sha256-KWdfV9a6+zG6Ij/7PZYLnomjBZZUu8gdRy+hfjGrrJQ=", 109 | "owner": "ipetkov", 110 | "repo": "crane", 111 | "rev": "1bde9c762ebf26de9f8ecf502357c92105bc4577", 112 | "type": "github" 113 | }, 114 | "original": { 115 | "owner": "ipetkov", 116 | "repo": "crane", 117 | "type": "github" 118 | } 119 | }, 120 | "crate2nix": { 121 | "flake": false, 122 | "locked": { 123 | "lastModified": 1675642992, 124 | "narHash": "sha256-uDBDZuiq7qyg82Udp82/r4zg5HKfIzBQqgl2U9THiQM=", 125 | "owner": "kolloch", 126 | "repo": "crate2nix", 127 | "rev": "45fc83132c8c91c77a1cd61fe0c945411d1edba8", 128 | "type": "github" 129 | }, 130 | "original": { 131 | "owner": "kolloch", 132 | "repo": "crate2nix", 133 | "type": "github" 134 | } 135 | }, 136 | "crate2nix_2": { 137 | "flake": false, 138 | "locked": { 139 | "lastModified": 1675642992, 140 | "narHash": "sha256-uDBDZuiq7qyg82Udp82/r4zg5HKfIzBQqgl2U9THiQM=", 141 | "owner": "kolloch", 142 | "repo": "crate2nix", 143 | "rev": "45fc83132c8c91c77a1cd61fe0c945411d1edba8", 144 | "type": "github" 145 | }, 146 | "original": { 147 | "owner": "kolloch", 148 | "repo": "crate2nix", 149 | "type": "github" 150 | } 151 | }, 152 | "empty": { 153 | "flake": false, 154 | "locked": { 155 | "lastModified": 1683792623, 156 | "narHash": "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=", 157 | "owner": "steveej", 158 | "repo": "empty", 159 | "rev": "8e328e450e4cd32e072eba9e99fe92cf2a1ef5cf", 160 | "type": "github" 161 | }, 162 | "original": { 163 | "owner": "steveej", 164 | "repo": "empty", 165 | "type": "github" 166 | } 167 | }, 168 | "empty_2": { 169 | "flake": false, 170 | "locked": { 171 | "lastModified": 1683792623, 172 | "narHash": "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=", 173 | "owner": "steveej", 174 | "repo": "empty", 175 | "rev": "8e328e450e4cd32e072eba9e99fe92cf2a1ef5cf", 176 | "type": "github" 177 | }, 178 | "original": { 179 | "owner": "steveej", 180 | "repo": "empty", 181 | "type": "github" 182 | } 183 | }, 184 | "flake-compat": { 185 | "flake": false, 186 | "locked": { 187 | "lastModified": 1673956053, 188 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 189 | "owner": "edolstra", 190 | "repo": "flake-compat", 191 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 192 | "type": "github" 193 | }, 194 | "original": { 195 | "owner": "edolstra", 196 | "repo": "flake-compat", 197 | "type": "github" 198 | } 199 | }, 200 | "flake-compat_2": { 201 | "flake": false, 202 | "locked": { 203 | "lastModified": 1673956053, 204 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 205 | "owner": "edolstra", 206 | "repo": "flake-compat", 207 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 208 | "type": "github" 209 | }, 210 | "original": { 211 | "owner": "edolstra", 212 | "repo": "flake-compat", 213 | "type": "github" 214 | } 215 | }, 216 | "flake-compat_3": { 217 | "flake": false, 218 | "locked": { 219 | "lastModified": 1673956053, 220 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 221 | "owner": "edolstra", 222 | "repo": "flake-compat", 223 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 224 | "type": "github" 225 | }, 226 | "original": { 227 | "owner": "edolstra", 228 | "repo": "flake-compat", 229 | "type": "github" 230 | } 231 | }, 232 | "flake-compat_4": { 233 | "flake": false, 234 | "locked": { 235 | "lastModified": 1673956053, 236 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 237 | "owner": "edolstra", 238 | "repo": "flake-compat", 239 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 240 | "type": "github" 241 | }, 242 | "original": { 243 | "owner": "edolstra", 244 | "repo": "flake-compat", 245 | "type": "github" 246 | } 247 | }, 248 | "flake-parts": { 249 | "inputs": { 250 | "nixpkgs-lib": "nixpkgs-lib" 251 | }, 252 | "locked": { 253 | "lastModified": 1675295133, 254 | "narHash": "sha256-dU8fuLL98WFXG0VnRgM00bqKX6CEPBLybhiIDIgO45o=", 255 | "owner": "hercules-ci", 256 | "repo": "flake-parts", 257 | "rev": "bf53492df08f3178ce85e0c9df8ed8d03c030c9f", 258 | "type": "github" 259 | }, 260 | "original": { 261 | "id": "flake-parts", 262 | "type": "indirect" 263 | } 264 | }, 265 | "flake-parts_2": { 266 | "inputs": { 267 | "nixpkgs-lib": "nixpkgs-lib_2" 268 | }, 269 | "locked": { 270 | "lastModified": 1675295133, 271 | "narHash": "sha256-dU8fuLL98WFXG0VnRgM00bqKX6CEPBLybhiIDIgO45o=", 272 | "owner": "hercules-ci", 273 | "repo": "flake-parts", 274 | "rev": "bf53492df08f3178ce85e0c9df8ed8d03c030c9f", 275 | "type": "github" 276 | }, 277 | "original": { 278 | "id": "flake-parts", 279 | "type": "indirect" 280 | } 281 | }, 282 | "flake-utils": { 283 | "locked": { 284 | "lastModified": 1667395993, 285 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 286 | "owner": "numtide", 287 | "repo": "flake-utils", 288 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 289 | "type": "github" 290 | }, 291 | "original": { 292 | "owner": "numtide", 293 | "repo": "flake-utils", 294 | "type": "github" 295 | } 296 | }, 297 | "flake-utils_2": { 298 | "locked": { 299 | "lastModified": 1667395993, 300 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 301 | "owner": "numtide", 302 | "repo": "flake-utils", 303 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 304 | "type": "github" 305 | }, 306 | "original": { 307 | "owner": "numtide", 308 | "repo": "flake-utils", 309 | "type": "github" 310 | } 311 | }, 312 | "flake-utils_3": { 313 | "inputs": { 314 | "systems": "systems" 315 | }, 316 | "locked": { 317 | "lastModified": 1681202837, 318 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 319 | "owner": "numtide", 320 | "repo": "flake-utils", 321 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 322 | "type": "github" 323 | }, 324 | "original": { 325 | "owner": "numtide", 326 | "repo": "flake-utils", 327 | "type": "github" 328 | } 329 | }, 330 | "flake-utils_4": { 331 | "inputs": { 332 | "systems": "systems_2" 333 | }, 334 | "locked": { 335 | "lastModified": 1681202837, 336 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 337 | "owner": "numtide", 338 | "repo": "flake-utils", 339 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 340 | "type": "github" 341 | }, 342 | "original": { 343 | "owner": "numtide", 344 | "repo": "flake-utils", 345 | "type": "github" 346 | } 347 | }, 348 | "holochain": { 349 | "inputs": { 350 | "cargo-chef": "cargo-chef_2", 351 | "cargo-rdme": "cargo-rdme_2", 352 | "crane": "crane_2", 353 | "crate2nix": "crate2nix_2", 354 | "empty": "empty_2", 355 | "flake-compat": "flake-compat_4", 356 | "flake-parts": "flake-parts_2", 357 | "holochain": [ 358 | "holochain-dev", 359 | "holochain", 360 | "empty" 361 | ], 362 | "lair": [ 363 | "holochain-dev", 364 | "holochain", 365 | "empty" 366 | ], 367 | "launcher": [ 368 | "holochain-dev", 369 | "holochain", 370 | "empty" 371 | ], 372 | "nix-filter": "nix-filter", 373 | "nixpkgs": "nixpkgs", 374 | "pre-commit-hooks-nix": "pre-commit-hooks-nix", 375 | "rust-overlay": "rust-overlay_3", 376 | "scaffolding": [ 377 | "holochain-dev", 378 | "holochain", 379 | "empty" 380 | ], 381 | "versions": "versions" 382 | }, 383 | "locked": { 384 | "lastModified": 1690227710, 385 | "narHash": "sha256-HRVEz5Ldg2+pqciOpZ9fNdMfU93r/3LmUsbTA9jfDIY=", 386 | "owner": "holochain", 387 | "repo": "holochain", 388 | "rev": "3f594f1a5cef41e896b99b6b46d336d54da3299d", 389 | "type": "github" 390 | }, 391 | "original": { 392 | "owner": "holochain", 393 | "ref": "holochain-0.2.1", 394 | "repo": "holochain", 395 | "type": "github" 396 | } 397 | }, 398 | "holochain-dev": { 399 | "inputs": { 400 | "cargo-chef": "cargo-chef", 401 | "cargo-rdme": "cargo-rdme", 402 | "crane": "crane", 403 | "crate2nix": "crate2nix", 404 | "empty": "empty", 405 | "flake-compat": "flake-compat_2", 406 | "flake-parts": "flake-parts", 407 | "holochain": "holochain", 408 | "lair": [ 409 | "holochain-dev", 410 | "empty" 411 | ], 412 | "launcher": [ 413 | "holochain-dev", 414 | "empty" 415 | ], 416 | "nix-filter": "nix-filter_2", 417 | "nixpkgs": "nixpkgs_2", 418 | "pre-commit-hooks-nix": "pre-commit-hooks-nix_2", 419 | "rust-overlay": "rust-overlay_4", 420 | "scaffolding": [ 421 | "holochain-dev", 422 | "empty" 423 | ], 424 | "versions": "versions_2" 425 | }, 426 | "locked": { 427 | "lastModified": 1692233696, 428 | "narHash": "sha256-Ly6ejtg9SSDSN0lCIULuvb/v78BTRDsGy4PvFrVj7/U=", 429 | "owner": "holochain", 430 | "repo": "holochain", 431 | "rev": "ad79d6fe0895d890bd6356ea4e33d83465f92b72", 432 | "type": "github" 433 | }, 434 | "original": { 435 | "owner": "holochain", 436 | "repo": "holochain", 437 | "type": "github" 438 | } 439 | }, 440 | "holochain_2": { 441 | "flake": false, 442 | "locked": { 443 | "lastModified": 1686257124, 444 | "narHash": "sha256-SvXGHOr96ob/NfQCeVJ2J4LWc83qkZn+/pnE9qVNB+I=", 445 | "owner": "holochain", 446 | "repo": "holochain", 447 | "rev": "db5b8b27da3bf296958c3bf54ac3950dc60a39c8", 448 | "type": "github" 449 | }, 450 | "original": { 451 | "owner": "holochain", 452 | "ref": "holochain-0.1.5", 453 | "repo": "holochain", 454 | "type": "github" 455 | } 456 | }, 457 | "holochain_3": { 458 | "flake": false, 459 | "locked": { 460 | "lastModified": 1690227710, 461 | "narHash": "sha256-HRVEz5Ldg2+pqciOpZ9fNdMfU93r/3LmUsbTA9jfDIY=", 462 | "owner": "holochain", 463 | "repo": "holochain", 464 | "rev": "3f594f1a5cef41e896b99b6b46d336d54da3299d", 465 | "type": "github" 466 | }, 467 | "original": { 468 | "owner": "holochain", 469 | "ref": "holochain-0.2.1", 470 | "repo": "holochain", 471 | "type": "github" 472 | } 473 | }, 474 | "lair": { 475 | "flake": false, 476 | "locked": { 477 | "lastModified": 1682356264, 478 | "narHash": "sha256-5ZYJ1gyyL3hLR8hCjcN5yremg8cSV6w1iKCOrpJvCmc=", 479 | "owner": "holochain", 480 | "repo": "lair", 481 | "rev": "43be404da0fd9d57bf4429c44def405bd6490f61", 482 | "type": "github" 483 | }, 484 | "original": { 485 | "owner": "holochain", 486 | "ref": "lair_keystore-v0.2.4", 487 | "repo": "lair", 488 | "type": "github" 489 | } 490 | }, 491 | "lair_2": { 492 | "flake": false, 493 | "locked": { 494 | "lastModified": 1682356264, 495 | "narHash": "sha256-5ZYJ1gyyL3hLR8hCjcN5yremg8cSV6w1iKCOrpJvCmc=", 496 | "owner": "holochain", 497 | "repo": "lair", 498 | "rev": "43be404da0fd9d57bf4429c44def405bd6490f61", 499 | "type": "github" 500 | }, 501 | "original": { 502 | "owner": "holochain", 503 | "ref": "lair_keystore-v0.2.4", 504 | "repo": "lair", 505 | "type": "github" 506 | } 507 | }, 508 | "launcher": { 509 | "flake": false, 510 | "locked": { 511 | "lastModified": 1677270906, 512 | "narHash": "sha256-/xT//6nqhjpKLMMv41JE0W3H5sE9jKMr8Dedr88D4N8=", 513 | "owner": "holochain", 514 | "repo": "launcher", 515 | "rev": "1ad188a43900c139e52df10a21e3722f41dfb967", 516 | "type": "github" 517 | }, 518 | "original": { 519 | "owner": "holochain", 520 | "ref": "holochain-0.1", 521 | "repo": "launcher", 522 | "type": "github" 523 | } 524 | }, 525 | "launcher_2": { 526 | "flake": false, 527 | "locked": { 528 | "lastModified": 1684183666, 529 | "narHash": "sha256-rOE/W/BBYyZKOyypKb8X9Vpc4ty1TNRoI/fV5+01JPw=", 530 | "owner": "holochain", 531 | "repo": "launcher", 532 | "rev": "75ecdd0aa191ed830cc209a984a6030e656042ff", 533 | "type": "github" 534 | }, 535 | "original": { 536 | "owner": "holochain", 537 | "ref": "holochain-0.2", 538 | "repo": "launcher", 539 | "type": "github" 540 | } 541 | }, 542 | "nix-filter": { 543 | "locked": { 544 | "lastModified": 1675361037, 545 | "narHash": "sha256-CTbDuDxFD3U3g/dWUB+r+B/snIe+qqP1R+1myuFGe2I=", 546 | "owner": "numtide", 547 | "repo": "nix-filter", 548 | "rev": "e1b2f96c2a31415f362268bc48c3fccf47dff6eb", 549 | "type": "github" 550 | }, 551 | "original": { 552 | "owner": "numtide", 553 | "repo": "nix-filter", 554 | "type": "github" 555 | } 556 | }, 557 | "nix-filter_2": { 558 | "locked": { 559 | "lastModified": 1675361037, 560 | "narHash": "sha256-CTbDuDxFD3U3g/dWUB+r+B/snIe+qqP1R+1myuFGe2I=", 561 | "owner": "numtide", 562 | "repo": "nix-filter", 563 | "rev": "e1b2f96c2a31415f362268bc48c3fccf47dff6eb", 564 | "type": "github" 565 | }, 566 | "original": { 567 | "owner": "numtide", 568 | "repo": "nix-filter", 569 | "type": "github" 570 | } 571 | }, 572 | "nixpkgs": { 573 | "locked": { 574 | "lastModified": 1686869522, 575 | "narHash": "sha256-tbJ9B8WLCTnVP/LwESRlg0dII6Zyg2LmUU/mB9Lu98E=", 576 | "owner": "NixOS", 577 | "repo": "nixpkgs", 578 | "rev": "7c67f006ea0e7d0265f16d7df07cc076fdffd91f", 579 | "type": "github" 580 | }, 581 | "original": { 582 | "id": "nixpkgs", 583 | "ref": "nixos-unstable", 584 | "type": "indirect" 585 | } 586 | }, 587 | "nixpkgs-lib": { 588 | "locked": { 589 | "dir": "lib", 590 | "lastModified": 1675183161, 591 | "narHash": "sha256-Zq8sNgAxDckpn7tJo7V1afRSk2eoVbu3OjI1QklGLNg=", 592 | "owner": "NixOS", 593 | "repo": "nixpkgs", 594 | "rev": "e1e1b192c1a5aab2960bf0a0bd53a2e8124fa18e", 595 | "type": "github" 596 | }, 597 | "original": { 598 | "dir": "lib", 599 | "owner": "NixOS", 600 | "ref": "nixos-unstable", 601 | "repo": "nixpkgs", 602 | "type": "github" 603 | } 604 | }, 605 | "nixpkgs-lib_2": { 606 | "locked": { 607 | "dir": "lib", 608 | "lastModified": 1675183161, 609 | "narHash": "sha256-Zq8sNgAxDckpn7tJo7V1afRSk2eoVbu3OjI1QklGLNg=", 610 | "owner": "NixOS", 611 | "repo": "nixpkgs", 612 | "rev": "e1e1b192c1a5aab2960bf0a0bd53a2e8124fa18e", 613 | "type": "github" 614 | }, 615 | "original": { 616 | "dir": "lib", 617 | "owner": "NixOS", 618 | "ref": "nixos-unstable", 619 | "repo": "nixpkgs", 620 | "type": "github" 621 | } 622 | }, 623 | "nixpkgs_2": { 624 | "locked": { 625 | "lastModified": 1686869522, 626 | "narHash": "sha256-tbJ9B8WLCTnVP/LwESRlg0dII6Zyg2LmUU/mB9Lu98E=", 627 | "owner": "NixOS", 628 | "repo": "nixpkgs", 629 | "rev": "7c67f006ea0e7d0265f16d7df07cc076fdffd91f", 630 | "type": "github" 631 | }, 632 | "original": { 633 | "id": "nixpkgs", 634 | "ref": "nixos-unstable", 635 | "type": "indirect" 636 | } 637 | }, 638 | "pre-commit-hooks-nix": { 639 | "flake": false, 640 | "locked": { 641 | "lastModified": 1676513100, 642 | "narHash": "sha256-MK39nQV86L2ag4TmcK5/+r1ULpzRLPbbfvWbPvIoYJE=", 643 | "owner": "cachix", 644 | "repo": "pre-commit-hooks.nix", 645 | "rev": "5f0cba88ac4d6dd8cad5c6f6f1540b3d6a21a798", 646 | "type": "github" 647 | }, 648 | "original": { 649 | "owner": "cachix", 650 | "repo": "pre-commit-hooks.nix", 651 | "type": "github" 652 | } 653 | }, 654 | "pre-commit-hooks-nix_2": { 655 | "flake": false, 656 | "locked": { 657 | "lastModified": 1676513100, 658 | "narHash": "sha256-MK39nQV86L2ag4TmcK5/+r1ULpzRLPbbfvWbPvIoYJE=", 659 | "owner": "cachix", 660 | "repo": "pre-commit-hooks.nix", 661 | "rev": "5f0cba88ac4d6dd8cad5c6f6f1540b3d6a21a798", 662 | "type": "github" 663 | }, 664 | "original": { 665 | "owner": "cachix", 666 | "repo": "pre-commit-hooks.nix", 667 | "type": "github" 668 | } 669 | }, 670 | "root": { 671 | "inputs": { 672 | "holochain-dev": "holochain-dev", 673 | "nixpkgs": [ 674 | "holochain-dev", 675 | "nixpkgs" 676 | ] 677 | } 678 | }, 679 | "rust-overlay": { 680 | "inputs": { 681 | "flake-utils": [ 682 | "holochain-dev", 683 | "crane", 684 | "flake-utils" 685 | ], 686 | "nixpkgs": [ 687 | "holochain-dev", 688 | "crane", 689 | "nixpkgs" 690 | ] 691 | }, 692 | "locked": { 693 | "lastModified": 1675391458, 694 | "narHash": "sha256-ukDKZw922BnK5ohL9LhwtaDAdCsJL7L6ScNEyF1lO9w=", 695 | "owner": "oxalica", 696 | "repo": "rust-overlay", 697 | "rev": "383a4acfd11d778d5c2efcf28376cbd845eeaedf", 698 | "type": "github" 699 | }, 700 | "original": { 701 | "owner": "oxalica", 702 | "repo": "rust-overlay", 703 | "type": "github" 704 | } 705 | }, 706 | "rust-overlay_2": { 707 | "inputs": { 708 | "flake-utils": [ 709 | "holochain-dev", 710 | "holochain", 711 | "crane", 712 | "flake-utils" 713 | ], 714 | "nixpkgs": [ 715 | "holochain-dev", 716 | "holochain", 717 | "crane", 718 | "nixpkgs" 719 | ] 720 | }, 721 | "locked": { 722 | "lastModified": 1675391458, 723 | "narHash": "sha256-ukDKZw922BnK5ohL9LhwtaDAdCsJL7L6ScNEyF1lO9w=", 724 | "owner": "oxalica", 725 | "repo": "rust-overlay", 726 | "rev": "383a4acfd11d778d5c2efcf28376cbd845eeaedf", 727 | "type": "github" 728 | }, 729 | "original": { 730 | "owner": "oxalica", 731 | "repo": "rust-overlay", 732 | "type": "github" 733 | } 734 | }, 735 | "rust-overlay_3": { 736 | "inputs": { 737 | "flake-utils": "flake-utils_3", 738 | "nixpkgs": [ 739 | "holochain-dev", 740 | "holochain", 741 | "nixpkgs" 742 | ] 743 | }, 744 | "locked": { 745 | "lastModified": 1689647697, 746 | "narHash": "sha256-8ZX/DVpKLmr85FRKILb+2p+JuxfLQ49LjXG/gmwsoIU=", 747 | "owner": "oxalica", 748 | "repo": "rust-overlay", 749 | "rev": "ed2774a9131ddad12e552ba04bd92f87df04a28b", 750 | "type": "github" 751 | }, 752 | "original": { 753 | "owner": "oxalica", 754 | "repo": "rust-overlay", 755 | "type": "github" 756 | } 757 | }, 758 | "rust-overlay_4": { 759 | "inputs": { 760 | "flake-utils": "flake-utils_4", 761 | "nixpkgs": [ 762 | "holochain-dev", 763 | "nixpkgs" 764 | ] 765 | }, 766 | "locked": { 767 | "lastModified": 1692151776, 768 | "narHash": "sha256-nE3Z0bRcABHQq2RFC1RsUVnGCJS0Y5xhYUBY9amxgJA=", 769 | "owner": "oxalica", 770 | "repo": "rust-overlay", 771 | "rev": "dea24da3d3be23ab53ee80314474afd5fcb03c1c", 772 | "type": "github" 773 | }, 774 | "original": { 775 | "owner": "oxalica", 776 | "repo": "rust-overlay", 777 | "type": "github" 778 | } 779 | }, 780 | "scaffolding": { 781 | "flake": false, 782 | "locked": { 783 | "lastModified": 1686617155, 784 | "narHash": "sha256-ZeWnh27JNb/abu/ii8e3u4DHns49MOFMNXGPGFPqS0k=", 785 | "owner": "holochain", 786 | "repo": "scaffolding", 787 | "rev": "861397c975542306be6d8529e5c6bdb21c7ba6a6", 788 | "type": "github" 789 | }, 790 | "original": { 791 | "owner": "holochain", 792 | "ref": "holochain-0.1", 793 | "repo": "scaffolding", 794 | "type": "github" 795 | } 796 | }, 797 | "scaffolding_2": { 798 | "flake": false, 799 | "locked": { 800 | "lastModified": 1692147670, 801 | "narHash": "sha256-jFt4LTUaUZTiOg2DLlbUqyeV2edfUxiJSljRjVJlObE=", 802 | "owner": "holochain", 803 | "repo": "scaffolding", 804 | "rev": "8a63d356a0856643769adc567e078796c6509511", 805 | "type": "github" 806 | }, 807 | "original": { 808 | "owner": "holochain", 809 | "ref": "holochain-0.2", 810 | "repo": "scaffolding", 811 | "type": "github" 812 | } 813 | }, 814 | "systems": { 815 | "locked": { 816 | "lastModified": 1681028828, 817 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 818 | "owner": "nix-systems", 819 | "repo": "default", 820 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 821 | "type": "github" 822 | }, 823 | "original": { 824 | "owner": "nix-systems", 825 | "repo": "default", 826 | "type": "github" 827 | } 828 | }, 829 | "systems_2": { 830 | "locked": { 831 | "lastModified": 1681028828, 832 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 833 | "owner": "nix-systems", 834 | "repo": "default", 835 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 836 | "type": "github" 837 | }, 838 | "original": { 839 | "owner": "nix-systems", 840 | "repo": "default", 841 | "type": "github" 842 | } 843 | }, 844 | "versions": { 845 | "inputs": { 846 | "holochain": "holochain_2", 847 | "lair": "lair", 848 | "launcher": "launcher", 849 | "scaffolding": "scaffolding" 850 | }, 851 | "locked": { 852 | "lastModified": 1686618210, 853 | "narHash": "sha256-lXY9ob0WAekcoEgWcFL3cJiPkwoKlsR2OMqG0S3vXzA=", 854 | "path": "./versions/0_1", 855 | "type": "path" 856 | }, 857 | "original": { 858 | "dir": "versions/0_1", 859 | "owner": "holochain", 860 | "repo": "holochain", 861 | "type": "github" 862 | } 863 | }, 864 | "versions_2": { 865 | "inputs": { 866 | "holochain": "holochain_3", 867 | "lair": "lair_2", 868 | "launcher": "launcher_2", 869 | "scaffolding": "scaffolding_2" 870 | }, 871 | "locked": { 872 | "dir": "versions/0_2", 873 | "lastModified": 1692253758, 874 | "narHash": "sha256-qyD3jxJ5mhqNiUMMucr5YHndSs/Y0JeAJq1kwOS1zq8=", 875 | "owner": "holochain", 876 | "repo": "holochain", 877 | "rev": "c2536b8ea2210265ee47dc5f43cbc590f4fdffad", 878 | "type": "github" 879 | }, 880 | "original": { 881 | "dir": "versions/0_1", 882 | "owner": "holochain", 883 | "repo": "holochain", 884 | "type": "github" 885 | } 886 | } 887 | }, 888 | "root": "root", 889 | "version": 7 890 | } 891 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Template for Holochain app development"; 3 | 4 | inputs = { 5 | versions.url = "github:holochain/holochain?dir=versions/0_1"; 6 | 7 | holochain-flake.url = "github:holochain/holochain"; 8 | holochain-flake.inputs.versions.follows = "versions"; 9 | holochain-flake.inputs.holochain.url = "github:holochain/holochain/holochain-0.2.1"; 10 | 11 | nixpkgs.follows = "holochain-flake/nixpkgs"; 12 | flake-parts.follows = "holochain-flake/flake-parts"; 13 | }; 14 | 15 | outputs = inputs: 16 | inputs.flake-parts.lib.mkFlake 17 | { 18 | inherit inputs; 19 | } 20 | { 21 | systems = builtins.attrNames inputs.holochain-flake.devShells; 22 | perSystem = 23 | { inputs' 24 | , config 25 | , pkgs 26 | , system 27 | , ... 28 | }: { 29 | devShells.default = pkgs.mkShell { 30 | inputsFrom = [ inputs'.holochain-flake.devShells.holonix ]; 31 | packages = with pkgs; [ 32 | pkgs.nodejs-18_x 33 | nodePackages.pnpm 34 | ]; 35 | }; 36 | }; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hc_zome_dna_auth_resolver_lib" 3 | version = "0.2.0" 4 | authors = ["pospi "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { workspace = true } 9 | hdk = { workspace = true } 10 | holo_hash = { workspace = true } 11 | 12 | hc_zome_dna_auth_resolver_rpc = { path = "../rpc" } 13 | hc_zome_dna_auth_resolver_storage = { path = "../storage" } 14 | hc_zome_dna_auth_resolver_core = { path = "../core" } 15 | 16 | [lib] 17 | path = "src/lib.rs" 18 | crate-type = ["cdylib", "rlib"] 19 | -------------------------------------------------------------------------------- /lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Library for use in application zomes which need to register themselves with 3 | * remote zomes for indexing. 4 | * 5 | * @package @holochain-open-dev/dna-auth-resolver 6 | * @since 2021-03-18 7 | */ 8 | use hdk::prelude::*; 9 | use holo_hash::{ActionHash, DnaHash}; 10 | 11 | pub use hc_zome_dna_auth_resolver_core::*; 12 | pub use hc_zome_dna_auth_resolver_rpc::*; 13 | pub use hc_zome_dna_auth_resolver_storage::*; 14 | 15 | // :TODO: make this dynamic so that DNA configurations don't have reserved zome names anymore 16 | pub const AUTH_ZOME_NAME: &str = "remote_auth"; 17 | pub const AUTH_ZOME_METHOD: &str = "register_dna"; 18 | 19 | /// Authentication data held by the local (calling) DNA about a connection to some remote (receiving) DNA, 20 | /// enabling it to lookup all necessary parameters to `call` to the correct remote zome. 21 | /// 22 | #[derive(Debug)] 23 | pub struct DNAConnectionAuth { 24 | pub method: GrantedFunction, 25 | pub claim: CapClaim, 26 | } 27 | 28 | /// fetches auth for some remote DNA if we are already authed, attempts one otherwise 29 | /// 30 | pub fn ensure_authed( 31 | to_dna: &DnaHash, 32 | remote_permission_id: &S, 33 | link_type: LT, 34 | ) -> ExternResult 35 | where 36 | S: AsRef, 37 | // links 38 | ScopedLinkType: TryFrom, 39 | LT: Clone + LinkTypeFilterExt, 40 | // entries 41 | EN: TryFrom, 42 | ScopedEntryDefIndex: for<'a> TryFrom<&'a EN, Error = E2>, 43 | EntryVisibility: for<'a> From<&'a EN>, 44 | Entry: TryFrom, 45 | // links and entries 46 | WasmError: From + From, 47 | { 48 | let mut cell_auth = get_auth_data(to_dna, remote_permission_id, link_type.clone()); 49 | match &cell_auth { 50 | Ok(_) => {} 51 | // transparently request indicated permission if not granted 52 | Err(_) => { 53 | let _ = make_auth_request(to_dna, remote_permission_id, link_type.clone())?; 54 | 55 | // re-check for permissions after request, bail if failed 56 | cell_auth = get_auth_data(to_dna, remote_permission_id, link_type); 57 | match cell_auth { 58 | Ok(_) => {} 59 | Err(e) => { 60 | return Err(wasm_error!(WasmErrorInner::Guest(format!( 61 | "Error in auth handshake from DNA {:?} to DNA {:?}: {:?}", 62 | dna_info()?.hash, 63 | to_dna.to_owned(), 64 | e.to_string() 65 | )))); 66 | } 67 | } 68 | } 69 | } 70 | 71 | let auth_data = cell_auth.unwrap(); 72 | 73 | Ok(auth_data) 74 | } 75 | 76 | /// trigger an initial authentication request to some remote DNA that is hosting the dna-auth-resolver zome API 77 | /// 78 | /// because this library is more of a mixin, we don't assume to know the LinkTypes or the EntryTypes 79 | /// (which can only come from the zome since they're based on numbered indexes) 80 | /// and therefore this method is generic and needs explicit type traits and a specific link_type passed in 81 | pub fn make_auth_request( 82 | to_dna: &DnaHash, 83 | remote_permission_id: &S, 84 | link_type: LT, 85 | ) -> ExternResult<()> 86 | where 87 | S: AsRef, 88 | // links 89 | ScopedLinkType: TryFrom, 90 | // entries 91 | EN: TryFrom, 92 | ScopedEntryDefIndex: for<'a> TryFrom<&'a EN, Error = E2>, 93 | EntryVisibility: for<'a> From<&'a EN>, 94 | Entry: TryFrom, 95 | // links and entries 96 | WasmError: From + From, 97 | { 98 | let permission_id = remote_permission_id.as_ref().to_string(); 99 | let secret = generate_cap_secret()?; 100 | let local_agent_key = agent_info()?.agent_latest_pubkey; 101 | let to_cell = CellId::new(to_dna.clone(), local_agent_key.clone()); 102 | 103 | // make request to the auth zome to ask for remote capability to be granted 104 | let resp = call( 105 | CallTargetCell::OtherCell(to_cell), 106 | ZomeName::from(AUTH_ZOME_NAME), 107 | FunctionName::from(AUTH_ZOME_METHOD), 108 | None, 109 | DnaRegistration { 110 | remote_dna: dna_info()?.hash, 111 | permission_id: permission_id.clone(), 112 | secret, 113 | }, 114 | )?; 115 | 116 | let mut maybe_grant_data: Option = None; 117 | let mut local_cap_action: Option = None; 118 | 119 | // handle response from auth zome and store provided capability access tokens 120 | (match resp { 121 | ZomeCallResponse::Ok(data) => { 122 | let remote_grant: ZomeCallCapGrant = data 123 | .decode() 124 | .map_err(|e| wasm_error!(WasmErrorInner::Serialize(e)))?; 125 | maybe_grant_data = Some(remote_grant.to_owned()); 126 | let remote_cap = remote_grant.access; 127 | 128 | match remote_cap { 129 | CapAccess::Assigned { secret, assignees } => { 130 | local_cap_action = Some(create_cap_claim(CapClaim::new( 131 | get_tag_for_auth(to_dna, &permission_id), 132 | assignees.iter().cloned().next().unwrap(), // take grantor as the author of the remote CapGrantEntry 133 | secret, 134 | ))?); 135 | } 136 | CapAccess::Transferable { secret } => { 137 | local_cap_action = Some(create_cap_claim(CapClaim::new( 138 | get_tag_for_auth(to_dna, &permission_id), 139 | local_agent_key, // :TODO: ensure this is correct metadata for Transferable grants 140 | secret, 141 | ))?); 142 | } 143 | CapAccess::Unrestricted => {} // :TODO: figure out if anything needs storing for these 144 | } 145 | 146 | Ok(()) 147 | } 148 | ZomeCallResponse::Unauthorized(_auth, cell, zome, fname, agent) => { 149 | Err(wasm_error!(WasmErrorInner::Guest(format!( 150 | "Auth request unauthorized: {:?} {:?} {:?} for agent {:?}", 151 | cell, zome, fname, agent 152 | )))) 153 | } 154 | ZomeCallResponse::NetworkError(msg) => Err(wasm_error!(WasmErrorInner::Guest(format!( 155 | "Network error in auth request: {:?}", 156 | msg 157 | )))), 158 | ZomeCallResponse::CountersigningSession(msg) => Err(wasm_error!(WasmErrorInner::Guest( 159 | format!("Countersigning session failed: {:?}", msg) 160 | ))), 161 | })?; 162 | 163 | if None == local_cap_action { 164 | return Err(wasm_error!(WasmErrorInner::Guest( 165 | "Internal error updating local CapClaim register".into() 166 | ))); 167 | } 168 | 169 | // retrieve EntryHash for CapClaim just stored 170 | let result = get( 171 | local_cap_action.unwrap(), 172 | GetOptions { 173 | strategy: GetStrategy::Latest, 174 | }, 175 | )?; 176 | let cap_claim_hash = get_entry_hash_for_element(result.as_ref())?; 177 | 178 | // store & link to allowed method list for calling back based on permission 179 | match maybe_grant_data { 180 | Some(ZomeCallCapGrant { 181 | functions: GrantedFunctions::Listed(list), 182 | .. 183 | }) if list.iter().cloned().next() != None => { 184 | // in this pattern only one method is allowed, per grant 185 | // which is why we can just pick the first in the list 186 | let method = list.iter().cloned().next(); 187 | 188 | // use the power of generics and conversion traits to 189 | // convert to whatever specific App Entry Type for the Zome 190 | // this code is mixed into is, and commit that 191 | let entry = EN::try_from(AvailableCapability { 192 | extern_id: permission_id.clone(), 193 | allowed_method: method.unwrap().clone(), 194 | })?; 195 | let method_action = create_entry(entry)?; 196 | 197 | let method_element = get( 198 | method_action, 199 | GetOptions { 200 | strategy: GetStrategy::Latest, 201 | }, 202 | )?; 203 | create_link( 204 | cap_claim_hash, 205 | get_entry_hash_for_element(method_element.as_ref())?, 206 | link_type, 207 | LinkTag::from(()), 208 | )?; 209 | 210 | Ok(()) 211 | } 212 | _ => Err(wasm_error!(WasmErrorInner::Guest( 213 | "Remote auth registration endpoint authorized no methods".into() 214 | ))), 215 | } 216 | } 217 | 218 | /// Read capability claim obtained from a previous `make_auth_request()`, in order to make an authenticated cross-DNA call. 219 | /// 220 | pub fn get_auth_data( 221 | to_registered_dna: &DnaHash, 222 | remote_permission_id: &S, 223 | link_type: LT, 224 | ) -> ExternResult 225 | where 226 | S: AsRef, 227 | LT: LinkTypeFilterExt, 228 | { 229 | let tag = get_tag_for_auth(to_registered_dna, remote_permission_id); 230 | let no_auth_err = Err(wasm_error!(WasmErrorInner::Guest(format!( 231 | "No auth data for {:?} in DNA {:?}", 232 | remote_permission_id.as_ref(), 233 | to_registered_dna.as_ref() 234 | )))); 235 | 236 | // lookup the matching CapClaim by filtering against authed DNA+permission tag 237 | let claims = query( 238 | ChainQueryFilter::new() 239 | .entry_type(EntryType::CapClaim) 240 | .include_entries(true), 241 | )?; 242 | let claim = claims 243 | .iter() 244 | .map(|c| { 245 | let h = get_entry_hash_for_element(Some(c)); 246 | let r = try_entry_from_element(Some(c)); 247 | match r { 248 | Err(_) => None, 249 | Ok(e) => Some((h, e.as_cap_claim())), 250 | } 251 | }) 252 | .filter(|c| { 253 | if !c.is_some() { 254 | return false; 255 | } 256 | let r = c.as_ref().unwrap(); 257 | r.1.is_some() && r.1.unwrap().tag() == tag 258 | }) 259 | .next(); 260 | 261 | match claim { 262 | // using CapClaim data, locate authenticated method data 263 | Some(Some((Ok(claim_hash), Some(claim)))) => { 264 | let links_result = get_links(claim_hash, link_type, Some(LinkTag::from(())))?; 265 | let method_entry_hash = links_result.iter().map(|l| l.target.clone()).next(); 266 | 267 | if None == method_entry_hash { 268 | return no_auth_err; 269 | } 270 | 271 | let method_element = get( 272 | method_entry_hash.unwrap().into_entry_hash().unwrap(), 273 | GetOptions { 274 | strategy: GetStrategy::Latest, 275 | }, 276 | )?; 277 | let method_entry = try_entry_from_element(method_element.as_ref())?; 278 | let method: AvailableCapability = try_decode_app_entry(method_entry.to_owned())?; 279 | 280 | // return CapClaim and GrantedFunction to the caller so they can `call()` the appropriate endpoint 281 | Ok(DNAConnectionAuth { 282 | claim: claim.to_owned(), 283 | method: method.allowed_method, 284 | }) 285 | } 286 | _ => no_auth_err, 287 | } 288 | } 289 | 290 | // helper to read related EntryHash for a Create or Update action Element 291 | fn get_entry_hash_for_element(element: Option<&Record>) -> ExternResult { 292 | element 293 | .and_then(|el| match el.action() { 294 | Action::Create(Create { entry_hash, .. }) => Some(entry_hash.to_owned()), 295 | Action::Update(Update { entry_hash, .. }) => Some(entry_hash.to_owned()), 296 | _ => None, 297 | }) 298 | .ok_or(wasm_error!(WasmErrorInner::Guest( 299 | "non-existent element".to_string() 300 | ))) 301 | } 302 | 303 | /// Helper for handling decoding of entry data to requested entry struct type 304 | /// 305 | /// :TODO: check the performance of this function, into_sb() is copying data 306 | /// :TODO: import this from a well-vetted shared lib 307 | /// 308 | fn try_decode_app_entry(entry: Entry) -> ExternResult 309 | where 310 | SerializedBytes: TryInto, 311 | { 312 | match entry { 313 | Entry::App(content) => { 314 | let decoded: T = content 315 | .into_sb() 316 | .try_into() 317 | .map_err(|e| wasm_error!(WasmErrorInner::Serialize(e)))?; 318 | Ok(decoded) 319 | } 320 | _ => Err(wasm_error!(WasmErrorInner::Guest( 321 | "wrong entry datatype".into() 322 | ))), 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hc_zome_dna_auth_resolver_rpc" 3 | version = "0.2.0" 4 | authors = ["pospi "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { workspace = true } 9 | holo_hash = { workspace = true } 10 | holochain_zome_types = { workspace = true } 11 | holochain_serialized_bytes = { workspace = true } 12 | 13 | [lib] 14 | crate-type = ["lib"] 15 | -------------------------------------------------------------------------------- /rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | use holo_hash::{DnaHash}; 2 | use holochain_zome_types::capability::CapSecret; 3 | use holochain_serialized_bytes::prelude::*; 4 | 5 | /// Payload to send to remote DNAs that the local DNA wants to authenticate with. 6 | /// Made unauthenticated, to allow subsequent requests to be authed against a CapClaim. 7 | /// 8 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 9 | pub struct DnaRegistration { 10 | pub remote_dna: DnaHash, 11 | pub permission_id: String, 12 | pub secret: CapSecret, 13 | } 14 | -------------------------------------------------------------------------------- /storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hc_zome_dna_auth_resolver_storage" 3 | version = "0.2.0" 4 | authors = ["pospi "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { workspace = true } 9 | hdk = { workspace = true } 10 | holo_hash = { workspace = true } 11 | 12 | hc_zome_dna_auth_resolver_core = { path = "../core" } 13 | 14 | [lib] 15 | crate-type = ["lib"] 16 | -------------------------------------------------------------------------------- /storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Structures for representing authenticated associations with foreign DNAs. 3 | * 4 | * @package Holo-REA 5 | */ 6 | use hdk::prelude::*; 7 | pub use holo_hash::{DnaHash}; 8 | use hc_zome_dna_auth_resolver_core::AvailableCapability; 9 | 10 | pub const CAP_STORAGE_ENTRY_DEF_ID: &str = "dna_authed_method_mapping"; 11 | 12 | // :TODO: remove this, replace with reference to appropriate namespacing of zome config 13 | #[derive(Clone, Serialize, Deserialize, SerializedBytes, PartialEq, Debug)] 14 | pub struct DnaConfigSlice { 15 | pub remote_auth: AvailableCapabilities, 16 | } 17 | 18 | /// Configuration structure for mapped open permissions, specified in DNA properties 19 | /// 20 | #[derive(Clone, Serialize, Deserialize, SerializedBytes, PartialEq, Debug)] 21 | pub struct AvailableCapabilities { 22 | pub permissions: Vec, 23 | } 24 | 25 | /// Helper to determine CapClaim tag for given requesting DNA hash & permission ID 26 | /// 27 | pub fn get_tag_for_auth(dna: &DnaHash, permission_id: &S) -> String 28 | where S: AsRef, 29 | { 30 | let mut s = permission_id.as_ref().to_string(); 31 | s.push(':'); 32 | s.push_str(&String::from_utf8_lossy(dna.as_ref()).to_string()); 33 | s 34 | } 35 | 36 | /// Helper to handle retrieving linked element entry from an element 37 | /// 38 | /// :TODO: import this from a well-vetted shared lib 39 | /// 40 | pub fn try_entry_from_element<'a>(element: Option<&'a Record>) -> ExternResult<&'a Entry> { 41 | element 42 | .and_then(|el| el.entry().as_option()) 43 | .ok_or(wasm_error!(WasmErrorInner::Guest("non-existent element".to_string()))) 44 | } 45 | -------------------------------------------------------------------------------- /zome/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hc_zome_dna_auth_resolver" 3 | version = "0.2.0" 4 | authors = ["pospi "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { workspace = true } 9 | hdk = { workspace = true } 10 | 11 | hc_zome_dna_auth_resolver_rpc = { path = "../rpc" } 12 | hc_zome_dna_auth_resolver_storage = { path = "../storage" } 13 | 14 | [lib] 15 | path = "src/lib.rs" 16 | crate-type = ["cdylib", "rlib"] 17 | -------------------------------------------------------------------------------- /zome/src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * DNA Auth Resolver zome 3 | * 4 | * Provides an API for foreign DNAs to register themselves with (this) local DNA. 5 | * 6 | * @see hc_zome_dna_auth_resolver_lib 7 | * 8 | * @package @holochain-open-dev/dna-auth-resolver 9 | * @since 2021-03-18 10 | */ 11 | use hdk::prelude::*; 12 | 13 | use hc_zome_dna_auth_resolver_rpc::*; 14 | use hc_zome_dna_auth_resolver_storage::*; 15 | 16 | /** 17 | * Accept a request from some remote DNA to register an authenticated connection with the local DNA. 18 | * 19 | * Reads the zome properties to determine the (statically known) list of available external "permissions" 20 | * for this DNA, and the internal zome & method names to which they correspond. These are passed into a 21 | * newly created capability grant in the local DNA and stored to allow the authentication. 22 | */ 23 | #[hdk_extern] 24 | fn register_dna(DnaRegistration { remote_dna, permission_id, secret }: DnaRegistration) -> ExternResult { 25 | let tag = get_tag_for_auth(&remote_dna, &permission_id); 26 | 27 | // lookup assigned capability ID 28 | let cap_fn_mapping: DnaConfigSlice = dna_info()?.modifiers.properties.try_into() 29 | .map_err(|e| wasm_error!(WasmErrorInner::Serialize(e)))?; 30 | let cap_fn = cap_fn_mapping.remote_auth.permissions.iter().find(|cap| { cap.extern_id == permission_id }); 31 | 32 | if None == cap_fn { return Err(wasm_error!(WasmErrorInner::CallError(format!("no permission with ID {:?}", permission_id)))); } 33 | 34 | // create capability grant for the remote requestor, based on the `secret` they provided and the currently executing (local) agent 35 | let mut assignees = BTreeSet::new(); 36 | assignees.insert(agent_info()?.agent_latest_pubkey); 37 | 38 | let mut allowed_method = BTreeSet::new(); 39 | allowed_method.insert(cap_fn.unwrap().allowed_method.to_owned()); 40 | 41 | let cap_action = create_cap_grant(CapGrantEntry::new( 42 | tag, 43 | CapAccess::Assigned { secret, assignees }, 44 | GrantedFunctions::Listed(allowed_method), 45 | ))?; 46 | 47 | // read capability grant back out to return it to the caller 48 | let result = get(cap_action, GetOptions { strategy: GetStrategy::Latest })?; 49 | let entry = try_entry_from_element(result.as_ref())?; 50 | 51 | match entry.as_cap_grant() { 52 | Some(CapGrant::RemoteAgent(grant)) => Ok(grant), 53 | Some(_) => Err(wasm_error!(WasmErrorInner::Guest("Wrong capability type assigned in create_cap_grant()! This should never happen.".to_string()))), 54 | None => Err(wasm_error!(WasmErrorInner::Guest("Consistency error storing capability grant! This should never happen.".to_string()))), 55 | } 56 | } 57 | --------------------------------------------------------------------------------