Skip to content

Commit

Permalink
Merge branch 'master' into admin
Browse files Browse the repository at this point in the history
  • Loading branch information
julianjelfs authored Jun 25, 2024
2 parents 89b4c31 + ff89dc3 commit ebc07fb
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 12 deletions.
4 changes: 4 additions & 0 deletions backend/canisters/user/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [unreleased]

### Changed

- Debug potential swap slippage for single user ([#5957](https://github.com/open-chat-labs/open-chat/pull/5957))

## [[2.0.1211](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1211-user)] - 2024-06-21

### Added
Expand Down
19 changes: 19 additions & 0 deletions backend/canisters/user/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use fire_and_forget_handler::FireAndForgetHandler;
use model::chit::ChitEarnedEvents;
use model::contacts::Contacts;
use model::favourite_chats::FavouriteChats;
use model::token_swaps::TokenSwap;
use notifications_canister::c2c_push_notification;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
Expand Down Expand Up @@ -146,6 +147,22 @@ impl RuntimeState {
}

pub fn metrics(&self) -> Metrics {
// TODO: Remove token_swaps from Metrics after next release
let mut token_swaps: Vec<TokenSwap> = Vec::new();
let my_user_id: UserId = self.env.canister_id().into();
let debug_user: UserId = Principal::from_text("pzg6u-5qaaa-aaaar-azjpa-cai").unwrap().into();

if my_user_id == debug_user {
// Show swaps from 2024/06/06 12:00 to 2024/06/07 12:00
token_swaps = self
.data
.token_swaps
.iter()
.filter(|ts| ts.started > 1717671600000 && ts.started < 1717758000000)
.cloned()
.collect();
}

Metrics {
heap_memory_used: utils::memory::heap(),
stable_memory_used: utils::memory::stable(),
Expand All @@ -171,6 +188,7 @@ impl RuntimeState {
escrow: self.data.escrow_canister_id,
icp_ledger: Cryptocurrency::InternetComputer.ledger_canister_id().unwrap(),
},
token_swaps,
}
}
}
Expand Down Expand Up @@ -335,6 +353,7 @@ pub struct Metrics {
pub video_call_operators: Vec<Principal>,
pub timer_jobs: u32,
pub canister_ids: CanisterIds,
pub token_swaps: Vec<TokenSwap>,
}

fn run_regular_jobs() {
Expand Down
9 changes: 7 additions & 2 deletions frontend/app/src/components/SignInWithMagicLink.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount, getContext } from "svelte";
import { onMount, getContext, createEventDispatcher } from "svelte";
import type { OpenChat } from "openchat-client";
import { i18nKey } from "../i18n/i18n";
import Translatable from "./Translatable.svelte";
Expand All @@ -9,6 +9,7 @@
import { Pincode, PincodeInput } from "svelte-pincode";
const client = getContext<OpenChat>("client");
const dispatch = createEventDispatcher();
let qs = window.location.search;
let status: string | undefined = undefined;
Expand Down Expand Up @@ -36,9 +37,13 @@
client
.handleMagicLink(qs)
.then((resp) => {
status = "magicLink." + resp.kind;
if (resp.kind === "success") {
dispatch("close");
} else if (resp.kind === "session_not_found") {
message = "magicLink.continueMessage";
status = "magicLink.success";
} else {
status = "magicLink." + resp.kind;
}
})
.catch((_) => {
Expand Down
8 changes: 4 additions & 4 deletions frontend/app/src/components/home/profile/SwapCrypto.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
$: anySwapsAvailable = Object.keys(swaps).length > 0 && detailsOut !== undefined;
$: swapping = state === "swap" && busy;
$: amountInText = client.formatTokens(amountIn, detailsIn.decimals);
$: minAmountOut =
bestQuote !== undefined ? (bestQuote[1] * BigInt(98)) / BigInt(100) : BigInt(0);
$: {
valid =
Expand Down Expand Up @@ -138,16 +136,18 @@
}
function swap() {
if (!valid) return;
if (!valid || bestQuote === undefined) return;
busy = true;
error = undefined;
result = undefined;
swapId = random128();
let minAmountOut = (bestQuote[1] * BigInt(98)) / BigInt(100);
client
.swapTokens(swapId, ledgerIn, ledgerOut!, amountIn, minAmountOut, bestQuote![0])
.swapTokens(swapId, ledgerIn, ledgerOut!, amountIn, minAmountOut, bestQuote[0])
.catch(() => (swapId = undefined))
.finally(() => (busy = false));
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/components/landingpages/HomePage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

{#if showSignInWithMagicLinkModal}
<Overlay>
<SignInWithMagicLink />
<SignInWithMagicLink on:close={() => (showSignInWithMagicLinkModal = false)} />
</Overlay>
{/if}

Expand Down
52 changes: 47 additions & 5 deletions frontend/openchat-client/src/openchat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,9 @@ import {
shouldThrottle,
throttleDeadline,
} from "./stores/throttling";
import { storeEmailSignInSession } from "openchat-shared";
import { getEmailSignInSession } from "openchat-shared";
import { removeEmailSignInSession } from "openchat-shared";

const MARK_ONLINE_INTERVAL = 61 * 1000;
const SESSION_TIMEOUT_NANOS = BigInt(30 * 24 * 60 * 60 * 1000 * 1000 * 1000); // 30 days
Expand Down Expand Up @@ -6271,12 +6274,36 @@ export class OpenChat extends OpenChatAgentWorker {
return this.sendRequest({ kind: "updateBtcBalance", userId: this._liveState.user.userId });
}

generateMagicLink(
async generateMagicLink(
email: string,
sessionKey: ECDSAKeyIdentity,
): Promise<GenerateMagicLinkResponse> {
const sessionKeyDer = toDer(sessionKey);
return this.sendRequest({ kind: "generateMagicLink", email, sessionKey: sessionKeyDer });

const resp = await this.sendRequest({
kind: "generateMagicLink",
email,
sessionKey: sessionKeyDer,
}).catch(
(error) =>
({
kind: "failed_to_send_email",
error: error.toString(),
}) as GenerateMagicLinkResponse,
);

if (resp.kind === "success") {
await storeEmailSignInSession(this._authClientStorage, {
key: sessionKey,
email,
userKey: resp.userKey,
expiration: resp.expiration,
});
} else {
await removeEmailSignInSession(this._authClientStorage);
}

return resp;
}

async handleMagicLink(qs: string): Promise<HandleMagicLinkResponse> {
Expand All @@ -6285,15 +6312,30 @@ export class OpenChat extends OpenChatAgentWorker {
const response = await fetch(`https://${signInWithEmailCanister}.raw.icp0.io/auth${qs}`);

if (response.ok) {
return { kind: "success" } as HandleMagicLinkResponse;
const session = await getEmailSignInSession(this._authClientStorage);
if (session === undefined) {
return { kind: "session_not_found" };
}

await this.getSignInWithEmailDelegation(
session.email,
session.userKey,
session.key,
session.expiration,
).catch((error) => ({
kind: "error",
error: error.toString(),
}));

return { kind: "success" };
} else if (response.status === 400) {
const body = await response.text();
if (body === "Link expired") {
return { kind: "link_expired" } as HandleMagicLinkResponse;
return { kind: "link_expired" };
}
}

return { kind: "link_invalid" } as HandleMagicLinkResponse;
return { kind: "link_invalid" };
}

async getSignInWithEmailDelegation(
Expand Down
1 change: 1 addition & 0 deletions frontend/openchat-shared/src/domain/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export type GenerateMagicLinkResponse =

export type HandleMagicLinkResponse =
| { kind: "success" }
| { kind: "session_not_found" }
| { kind: "link_invalid" }
| { kind: "link_expired" };
55 changes: 55 additions & 0 deletions frontend/openchat-shared/src/utils/emailSignInSessionStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { AuthClientStorage } from "@dfinity/auth-client";
import { ECDSAKeyIdentity } from "@dfinity/identity";

const KEY_STORAGE_EMAIL_LINK = "email_link";
const KEY_STORAGE_EMAIL_LINK_CONTEXT = "email_link_context";

export type EmailSignInSession = EmailSignInContext & {
key: ECDSAKeyIdentity;
};

export type EmailSignInContext = {
email: string;
userKey: Uint8Array;
expiration: bigint;
};

export async function storeEmailSignInSession(
storage: AuthClientStorage,
session: EmailSignInSession,
): Promise<void> {
await storage.set(KEY_STORAGE_EMAIL_LINK, session.key.getKeyPair());

await storage.set(
KEY_STORAGE_EMAIL_LINK_CONTEXT,
JSON.stringify({
email: session.email,
userKey: Array.from(session.userKey),
expiration: session.expiration.toString(),
}),
);
}

export async function getEmailSignInSession(
storage: AuthClientStorage,
): Promise<EmailSignInSession | undefined> {
const keyPair = (await storage.get(KEY_STORAGE_EMAIL_LINK)) as CryptoKeyPair | null;
if (keyPair == null) return undefined;
const contextString = (await storage.get(KEY_STORAGE_EMAIL_LINK_CONTEXT)) as string | null;
if (contextString == null) return undefined;

const key = await ECDSAKeyIdentity.fromKeyPair(keyPair);
const context = JSON.parse(contextString);

return {
key,
email: context.email,
userKey: Uint8Array.from(context.userKey),
expiration: BigInt(context.expiration),
};
}

export async function removeEmailSignInSession(storage: AuthClientStorage): Promise<void> {
storage.remove(KEY_STORAGE_EMAIL_LINK);
storage.remove(KEY_STORAGE_EMAIL_LINK_CONTEXT);
}
1 change: 1 addition & 0 deletions frontend/openchat-shared/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { getTimeUntilSessionExpiryMs } from "./session";
export * from "./emailSignInSessionStorage";
export * from "./i18n";
export * from "./identity";
export * from "./identityStorage";
Expand Down

0 comments on commit ebc07fb

Please sign in to comment.