Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read canister ID from canister #528

Merged
merged 15 commits into from
Feb 4, 2022
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ FROM deps as build

COPY . .

ENV CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai
ARG II_ENV=production

RUN npm ci
Expand Down
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ Then open `http://localhost:8080` in your browser. Webpack will reload the page
npm run format && npm run lint
```

To customize your canister ID for deployment or particular local development, create a [`.env`](https://www.npmjs.com/package/dotenv) file in the root of the project and add a `CANISTER_ID` attribute. It should look something like
```
CANISTER_ID=rrkah-fqaaa-aaaaa-aaaaq-cai
```

Finally, to test workflows like authentication from a client application, you start the sample app:

```bash
Expand Down
20 changes: 4 additions & 16 deletions src/frontend/assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,10 @@
<main id="pageContent" aria-live="polite"></main>
<div id="notification"></div>
<div id="loaderContainer"></div>
<!--
Note: we cannot use a normal script tag like this
<script src="index.js" integrity="sha256-QKc0t+gyMRWWDNty0lxQKWpPz18K4pD8q3S0YoeQMdo=" defer></script>
because Firefox does not support SRI with CSP: https://bugzilla.mozilla.org/show_bug.cgi?id=1409200

DO NOT MODIFY THE INLINE SCRIPT CONTENT!
(or if you do, update the CSP hash)
-->
<script>
const scripts = ["index.js"];
scripts.forEach(function (scriptUrl) {
let s = document.createElement("script");
s.async = false;
s.src = scriptUrl;
document.head.appendChild(s);
});
</script>
<!-- this is replaced by the backend before serving -->
<!-- XXX: DO NOT CHANGE! or if you do, change the bit that matches on this
exact string in the canister code-->
<script id="setupJs"></script>
</body>
</html>
17 changes: 15 additions & 2 deletions src/frontend/src/utils/iiConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,23 @@ import { Principal } from "@dfinity/principal";
import { MultiWebAuthnIdentity } from "./multiWebAuthnIdentity";
import { hasOwnProperty } from "./utils";
import * as tweetnacl from "tweetnacl";
import { displayError } from "../components/displayError";
import { fromMnemonicWithoutValidation } from "../crypto/ed25519";

// eslint-disable-next-line
const canisterId: string = process.env.CANISTER_ID!;
declare const canisterId: string;

// Check if the canister ID was defined before we even try to read it
if (typeof canisterId !== undefined) {
displayError({
title: "Canister ID not set",
message:
"There was a problem contacting the IC. The host serving this page did not give us a canister ID. Try reloading the page and contact support if the problem persists.",
primaryButton: "Reload",
}).then(() => {
window.location.reload();
});
}

export const canisterIdPrincipal: Principal = Principal.fromText(canisterId);
export const baseActor = Actor.createActor<_SERVICE>(internet_identity_idl, {
agent: new HttpAgent({}),
Expand Down
1 change: 1 addition & 0 deletions src/internet_identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ic-cdk = "0.3.2"
ic-cdk-macros = "0.3"
ic-certified-map = "0.3.0"
ic-types = "0.1.1"
lazy_static = "1.4.0"
serde = "1"
serde_bytes = "0.11"
serde_cbor = "0.11"
Expand Down
54 changes: 37 additions & 17 deletions src/internet_identity/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// This file describes which assets are used and how (content, content type and content encoding).

use sha2::Digest;
use lazy_static::lazy_static;
use ic_cdk::api;

#[derive(Debug, PartialEq, Eq)]
pub enum ContentEncoding {
Expand All @@ -19,15 +21,43 @@ pub enum ContentType {
SVG
}

pub fn for_each_asset(mut f: impl FnMut(&'static str, ContentEncoding, ContentType, &'static [u8], &[u8; 32])) {
lazy_static! {
// The <script> tag that sets the canister ID and loads the 'index.js'
static ref INDEX_HTML_SETUP_JS: String = {
let canister_id = api::id();
format!(r#"var canisterId = '{canister_id}';let s = document.createElement('script');s.async = false;s.src = 'index.js';document.head.appendChild(s);"#)
};

let index_html = include_bytes!("../../../dist/index.html");
// The SRI sha256 hash of the script tag, used by the CSP policy.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
pub static ref INDEX_HTML_SETUP_JS_SRI_HASH: String = {
let hash = &sha2::Sha256::digest(INDEX_HTML_SETUP_JS.as_bytes());
let hash = base64::encode(hash);
format!("sha256-{hash}")
};

let assets: [ (&str, &[u8], ContentEncoding, ContentType); 8] = [
// The full content of the index.html, after the canister ID (and script tag) have been
// injected
static ref INDEX_HTML_STR: String = {
let index_html = include_str!("../../../dist/index.html");
let foo: String = INDEX_HTML_SETUP_JS.to_string();
let index_html = index_html.replace(
"<script id='setupJs'></script>",
nmattia marked this conversation as resolved.
Show resolved Hide resolved
&format!("<script id='setupJs'>{foo}</script>").to_string()
nmattia marked this conversation as resolved.
Show resolved Hide resolved
);
index_html
};
}

// Get all the assets. Duplicated assets like index.html are shared and generally all assets are
// prepared only once (like injecting the canister ID).
pub fn get_assets() -> [ (&'static str, &'static [u8], ContentEncoding, ContentType); 8] {
let index_html: &[u8] = INDEX_HTML_STR.as_bytes();
[
("/",
index_html,
ContentEncoding::Identity,
ContentType::HTML,
index_html,
ContentEncoding::Identity,
ContentType::HTML,
nmattia marked this conversation as resolved.
Show resolved Hide resolved
),
// The FAQ and about pages are the same webapp, but the webapp routes to the correct page
(
Expand Down Expand Up @@ -72,16 +102,6 @@ pub fn for_each_asset(mut f: impl FnMut(&'static str, ContentEncoding, ContentTy
ContentEncoding::Identity,
ContentType::SVG,
),
];

for (name, content, encoding, content_type) in assets {
let hash = hash_content(content);
f(name, encoding, content_type, content, &hash);
}
}

]

// Hash the content of an asset in an `ic_certified_map` friendly way
fn hash_content(bytes: &[u8]) -> [u8; 32] {
sha2::Sha256::digest(bytes).into()
}
23 changes: 15 additions & 8 deletions src/internet_identity/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use internet_identity::signature_map::SignatureMap;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use serde::Serialize;
use serde_bytes::{ByteBuf, Bytes};
use sha2::Digest;
use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
Expand Down Expand Up @@ -811,6 +812,7 @@ fn http_request(req: HttpRequest) -> HttpResponse {
/// These headers enable browser security features (like limit access to platform apis and set
/// iFrame policies, etc.).
fn security_headers() -> Vec<HeaderField> {
let hash = assets::INDEX_HTML_SETUP_JS_SRI_HASH.to_string();
vec![
("X-Frame-Options".to_string(), "DENY".to_string()),
("X-Content-Type-Options".to_string(), "nosniff".to_string()),
Expand All @@ -832,19 +834,24 @@ fn security_headers() -> Vec<HeaderField> {
// style-src 'unsafe-inline' is currently required due to the way styles are handled by the
// application. Adding hashes would require a big restructuring of the application and build
// infrastructure.
//
// NOTE about `script-src`: we cannot use a normal script tag like this
// <script src="index.js" integrity="sha256-..." defer></script>
// because Firefox does not support SRI with CSP: https://bugzilla.mozilla.org/show_bug.cgi?id=1409200
// Instead, we add it to the CSP policy
(
"Content-Security-Policy".to_string(),
"default-src 'none';\
format!("default-src 'none';\
connect-src 'self' https://ic0.app;\
img-src 'self' data:;\
script-src 'sha256-syYd+YuWeLD80uCtKwbaGoGom63a0pZE5KqgtA7W1d8=' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https:;\
script-src '{hash}' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https:;\
base-uri 'none';\
frame-ancestors 'none';\
form-action 'none';\
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;\
style-src-elem 'unsafe-inline' https://fonts.googleapis.com;\
font-src https://fonts.gstatic.com;\
upgrade-insecure-requests;"
upgrade-insecure-requests;")
.to_string()
),
(
Expand Down Expand Up @@ -922,9 +929,9 @@ fn init_assets() {

ASSETS.with(|a| {
let mut assets = a.borrow_mut();
assets::for_each_asset(|name, encoding, content_type, contents, hash| {
asset_hashes.insert(name, *hash);
let mut headers = match encoding {
for (path, content, content_encoding, content_type) in assets::get_assets() {
asset_hashes.insert(path, sha2::Sha256::digest(content).into());
let mut headers = match content_encoding {
ContentEncoding::Identity => vec![],
ContentEncoding::GZip => {
vec![("Content-Encoding".to_string(), "gzip".to_string())]
Expand All @@ -934,8 +941,8 @@ fn init_assets() {
"Content-Type".to_string(),
content_type.to_mime_type_string(),
));
assets.insert(name, (headers, contents));
});
assets.insert(path, (headers, content));
}
});
});
}
Expand Down
57 changes: 48 additions & 9 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@ const webpack = require("webpack");
const CopyPlugin = require("copy-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const HttpProxyMiddlware = require("http-proxy-middleware");
const dfxJson = require("./dfx.json");
require("dotenv").config();

let localCanister;

try {
localCanister = require("./.dfx/local/canister_ids.json").internet_identity.local;
} catch {}

/**
* Generate a webpack configuration for a canister.
*/
Expand Down Expand Up @@ -43,13 +38,58 @@ function generateWebpackConfigForCanister(name, info) {
path: path.join(__dirname, "dist"),
},
devServer: {

// Set up a proxy that redirects API calls and /index.html to the
// replica; the rest we serve from here.
onBeforeSetupMiddleware: (devServer) => {
const dfxJson = './dfx.json';
let replicaHost;

try {
replicaHost = require(dfxJson).networks.local.bind;
} catch (e) {
throw Error(`Could get host from ${dfxJson}: ${e}`);
}

if(!replicaHost.startsWith("http://")) {
nmattia marked this conversation as resolved.
Show resolved Hide resolved
replicaHost = `http://${replicaHost}`;
}

const canisterIdsJson = './.dfx/local/canister_ids.json';

let canisterId;

try {
canisterId = require(canisterIdsJson).internet_identity.local;
} catch (e) {
throw Error(`Could get canister ID from ${canisterIdsJson}: ${e}`);
}

// basically everything _except_ for index.js, because we want live reload
devServer.app.get(['/', '/index.html', '/faq', '/faq', 'about' ], HttpProxyMiddlware.createProxyMiddleware( {
target: replicaHost,
pathRewrite: (pathAndParams, req) => {
let queryParamsString = `?`;

const [path, params] = pathAndParams.split("?");

if (params) {
queryParamsString += `${params}&`;
}

queryParamsString += `canisterId=${canisterId}`;
nmattia marked this conversation as resolved.
Show resolved Hide resolved

return path + queryParamsString;
},

}));
},
port: 8080,
proxy: {
// Make sure /api calls land on the replica (and not on webpack)
"/api": "http://localhost:8000",
"/authorize": "http://localhost:8081",
},
allowedHosts: [".localhost", ".local", ".ngrok.io"],
historyApiFallback: true, // makes sure our index is served on all endpoints, e.g. `/faq`
},

// Depending in the language or framework you are using for
Expand Down Expand Up @@ -81,7 +121,6 @@ function generateWebpackConfigForCanister(name, info) {
process: require.resolve("process/browser"),
}),
new webpack.EnvironmentPlugin({
"CANISTER_ID": localCanister,
"II_ENV": "production"
}),
new CompressionPlugin({
Expand Down