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

feat(admin-api): enable auth headers for admin api routes #354

Merged
merged 27 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ca4ff7b
feat: initial update for node ui auth
frdomovic Jun 6, 2024
12518d1
feat: add headers checks
frdomovic Jun 7, 2024
e1a73ab
fix: remove unused imports
frdomovic Jun 17, 2024
1eaf50a
fix: remove get did function
frdomovic Jun 17, 2024
e612e44
feat: add auth layer to admin api
frdomovic Jun 17, 2024
169fc67
fix: remove client key route from protected routes
frdomovic Jun 17, 2024
174cd1c
feat: initial sdk auth headers update
frdomovic Jun 17, 2024
70cdce1
fix: remove auth for public routes
frdomovic Jun 18, 2024
056c747
fix: remove auth headers from login
frdomovic Jun 18, 2024
4052a46
feat: bump sdk version
frdomovic Jun 18, 2024
3acb815
fix: revert file
frdomovic Jun 18, 2024
c4194d8
fix: space fix
frdomovic Jun 18, 2024
76330b9
fix: replace headers map with axios header
frdomovic Jun 18, 2024
5007e49
fix: bump sdk version
frdomovic Jun 18, 2024
596fe75
Merge branch 'master' into C20-87_add_admin_ui_auth
frdomovic Jun 18, 2024
9109673
fix: rename unprotected router
frdomovic Jun 19, 2024
91a3402
fix: add root key to protected routes
frdomovic Jun 19, 2024
0fb0a4a
fix: requested changes
frdomovic Jun 19, 2024
7777f00
fix: lint
frdomovic Jun 19, 2024
af0a7e1
fix: typo
frdomovic Jun 19, 2024
c917e31
fix: variable camel case
frdomovic Jun 20, 2024
06edebd
fix: rename axios header to header
frdomovic Jun 20, 2024
0efeb1d
fix: remove default context id value
frdomovic Jun 20, 2024
61d56a3
Merge branch 'master' into C20-87_add_admin_ui_auth
frdomovic Jun 20, 2024
777dd20
fix: remove clone
frdomovic Jun 20, 2024
dfef99e
Merge branch 'master' into C20-87_add_admin_ui_auth
frdomovic Jun 21, 2024
579f480
feat: bump sdk version to 0.0.23
frdomovic Jun 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions crates/server/src/admin/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use serde_json::json;
use tower_sessions::{MemoryStore, SessionManagerLayer};
use tracing::info;

use crate::middleware;

use super::handlers;

#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -47,27 +49,18 @@ pub(crate) fn setup(
let session_layer = SessionManagerLayer::new(session_store).with_secure(false);

let shared_state = Arc::new(AdminState {
store,
store: store.clone(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if this will work across multiple calls. We had some issues with cloning store before but can't recall exactly what was it

keypair: config.identity.clone(),
application_manager,
});

let admin_router = Router::new()
.route("/health", get(health_check_handler))
let protected_router = Router::new()
.route(
"/root-key",
post(handlers::root_keys::create_root_key_handler),
)
.route(
"/request-challenge",
post(handlers::challenge::request_challenge_handler),
)
.route("/install-application", post(install_application_handler))
.route("/applications", get(list_applications_handler))
.route(
"/add-client-key",
post(handlers::add_client_key::add_client_key_handler),
)
.route("/did", get(handlers::fetch_did::fetch_did_handler))
.route("/contexts", post(handlers::context::create_context_handler))
.route(
Expand All @@ -91,7 +84,24 @@ pub(crate) fn setup(
get(handlers::context::get_context_storage_handler),
)
.route("/contexts", get(handlers::context::get_contexts_handler))
.layer(Extension(shared_state))
.layer(middleware::auth::AuthSignatureLayer::new(store))
.layer(Extension(shared_state.clone()));

let unprotected_router = Router::new()
.route("/health", get(health_check_handler))
frdomovic marked this conversation as resolved.
Show resolved Hide resolved
.route(
"/request-challenge",
post(handlers::challenge::request_challenge_handler),
)
.route(
"/add-client-key",
post(handlers::add_client_key::add_client_key_handler),
)
.layer(Extension(shared_state));

let admin_router = Router::new()
.nest("/", unprotected_router)
.nest("/", protected_router)
.layer(session_layer);

Ok(Some((admin_path, admin_router)))
Expand Down
2 changes: 1 addition & 1 deletion packages/calimero-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@calimero-is-near/calimero-p2p-sdk",
"version": "0.0.21",
"version": "0.0.22",
"description": "Javascript library to interact with Calimero P2P node",
"type": "module",
"main": "lib/index.js",
Expand Down
1 change: 1 addition & 0 deletions packages/calimero-sdk/src/api/nodeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface LoginRequest {
walletSignature: String;
payload: Payload;
walletMetadata: WalletMetadata;
contextId: string;
}

export interface RootKeyRequest {
Expand Down
56 changes: 56 additions & 0 deletions packages/calimero-sdk/src/auth/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { unmarshalPrivateKey } from '@libp2p/crypto/keys';
import { PrivateKey } from '@libp2p/interface';
import bs58 from 'bs58';
import { WalletType } from '../api/nodeApi';
import { ClientKey } from '../types/storage';
import { getStorageClientKey } from '../storage/storage';

export interface Header {
[key: string]: string;
}

export async function createAuthHeader(
payload: string,
contextId: string
): Promise<Header | null> {
const privateKey: PrivateKey = await getPrivateKey();

if (!privateKey) {
return null;
}

const encoder = new TextEncoder();
const contentBuff = encoder.encode(payload);

const signingKey = bs58.encode(privateKey.public.bytes);

const hashBuffer = await crypto.subtle.digest('SHA-256', contentBuff);
const hashArray = new Uint8Array(hashBuffer);

const signature = await privateKey.sign(hashArray);
const signatureBase58 = bs58.encode(signature);
const contentBase58 = bs58.encode(hashArray);

const headers: Header = {
wallet_type: JSON.stringify(WalletType.NEAR),
signing_key: signingKey,
signature: signatureBase58,
challenge: contentBase58,
context_id: contextId,
};

return headers;
}

export async function getPrivateKey(): Promise<PrivateKey | null> {
try {
const clientKey: ClientKey | null = getStorageClientKey();
if (!clientKey) {
return null;
}
return await unmarshalPrivateKey(bs58.decode(clientKey.privateKey));
} catch (error) {
console.error('Error extracting private key:', error);
return null;
}
}
2 changes: 2 additions & 0 deletions packages/calimero-sdk/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './headers';
export * from './ed25519';
1 change: 1 addition & 0 deletions packages/calimero-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './setup';
export * from './components';
export * from './types';
export * from './api/nodeApi';
export * from './auth';
23 changes: 21 additions & 2 deletions packages/calimero-sdk/src/storage/storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ClientKey } from '../types/storage';

export const CLIENT_KEY = 'client-key';
export const APP_URL = 'app-url';
export const AUTHORIZED = 'node-authorized';

export const setStorageClientKey = (clientKey: ClientKey) => {
Expand All @@ -10,7 +11,7 @@ export const setStorageClientKey = (clientKey: ClientKey) => {
export const getStorageClientKey = (): ClientKey | null => {
if (typeof window !== 'undefined' && window.localStorage) {
let clientKeystore: ClientKey = JSON.parse(
localStorage.getItem(CLIENT_KEY)!,
localStorage.getItem(CLIENT_KEY)
);
if (clientKeystore) {
return clientKeystore;
Expand All @@ -29,7 +30,7 @@ export const setStorageNodeAuthorized = () => {

export const getStorageNodeAuthorized = (): boolean | null => {
if (typeof window !== 'undefined' && window.localStorage) {
let authorized: boolean = JSON.parse(localStorage.getItem(AUTHORIZED)!);
let authorized: boolean = JSON.parse(localStorage.getItem(AUTHORIZED));
if (authorized) {
return authorized;
}
Expand All @@ -40,3 +41,21 @@ export const getStorageNodeAuthorized = (): boolean | null => {
export const clearNodeAuthorized = () => {
localStorage.removeItem(AUTHORIZED);
};

export const clearAppEndpoint = () => {
localStorage.removeItem(APP_URL);
};

export const setAppEndpointKey = (url: String) => {
localStorage.setItem(APP_URL, JSON.stringify(url));
};

export const getAppEndpointKey = (): String | null => {
if (typeof window !== 'undefined' && window.localStorage) {
let url: String = JSON.parse(localStorage.getItem(APP_URL));
if (url) {
return url;
}
}
return null;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import { randomBytes } from 'crypto';
import { getOrCreateKeypair } from '../../crypto/ed25519';
import { getOrCreateKeypair } from '../../auth/ed25519';

import {
useAccount,
Expand Down Expand Up @@ -118,6 +118,7 @@ export function LoginWithMetamask({
walletSignature: signData,
payload: walletSignatureData?.payload,
walletMetadata: walletMetadata,
contextId: applicationId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not part of this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-06-19 at 17 52 33 Auth wrapper already has this context_id header check thats why adding this is needed.

Copy link
Member

@MatejVukosav MatejVukosav Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, when I think more about it, I think it will not work. We don't know the context id during login. We only know the application id... and multiple contexts can use the same application

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will work when context id replaces application id. When this happens we can just put context id in envs of application e.g. only peers client.
Right now this works when you pass application id as context id and apart from checking if the header exists there are no other tests with context_id. This new field is only used in context details > client keys so it does not break anything else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you don't know context upfront

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what do you propose should be done?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets wait for Mira to deploy context and then we can check it. I think we will need to remove client keys from context and bring them to level of root keys, independent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested the admin ui and it works

};
await apiClient
.node()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ResponseData } from '../../types/api-response';
import { setStorageNodeAuthorized } from '../../storage/storage';
import { Loading } from '../loading/Loading';
import { getNetworkType } from '../eth/type';
import { getOrCreateKeypair } from '../../crypto/ed25519';
import { getOrCreateKeypair } from '../../auth/ed25519';
import { randomBytes } from 'crypto';

interface MetamaskRootKeyProps {
Expand Down
3 changes: 2 additions & 1 deletion packages/calimero-sdk/src/wallets/NearLogin/NearLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@near-wallet-selector/core';

import { useWalletSelector } from './WalletSelectorContext';
import { getOrCreateKeypair } from '../../crypto/ed25519';
import { getOrCreateKeypair } from '../../auth/ed25519';
import apiClient from '../../api';
import { ResponseData } from '../../types/api-response';
import { setStorageNodeAuthorized } from '../../storage/storage';
Expand Down Expand Up @@ -238,6 +238,7 @@ export const NearLogin: React.FC<NearLoginProps> = ({
walletSignature: signature,
payload: walletSignatureData.payload!,
walletMetadata: walletMetadata,
contextId: appId,
};

await apiClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
WalletMetadata,
SignatureMessage,
} from '../../api/nodeApi';
import { getOrCreateKeypair } from '../../crypto/ed25519';
import { getOrCreateKeypair } from '../../auth/ed25519';

type Account = AccountView & {
account_id: string;
Expand Down