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

Custom authentication using dfinity/agent #164

Open
CodingFu opened this issue Jun 21, 2024 · 7 comments
Open

Custom authentication using dfinity/agent #164

CodingFu opened this issue Jun 21, 2024 · 7 comments
Labels
question Further information is requested

Comments

@CodingFu
Copy link

CodingFu commented Jun 21, 2024

I am trying to implement third-party authentication mechanism on front-end.
I'm trying to log in telegram web app into juno (because both nfid and internet identity work poorly in this environment)

Setup:

I have profiles table in juno (read: Public, write: Managed)

And i construct my identity in the following way:

const customIdentity = Ed25519KeyIdentity.generate()

// this doesnt work because reads are public :(
const profile = await getDoc({
  collection: "profiles",
  key: "123",
  satellite: {
    identity: customIdentity,
    satelliteId: "n5isd-iaaaa-aaaal-adtra-cai",
  }
})

// this works because reads are public
const profile = await getDoc({
  collection: "profiles",
  key: "123",
  doc: {
    username: "CodingFu"
  },
  satellite: {
    identity: customIdentity,
    satelliteId: "n5isd-iaaaa-aaaal-adtra-cai",
  }
})

I understand for the fact that user does not exist on authentication tab.

How can I add user with custom provider there?
Is there any way i can "fake" authentication in juno?
I know that i can make my customIdentity a controller for the satellite, but I can't do it for all the users. Plus I want to maintain "owner" field.

Please help!

@peterpeterparker peterpeterparker added the question Further information is requested label Jun 22, 2024
@peterpeterparker
Copy link
Contributor

How can I add user with custom provider there?

This is not yet supported. Providing interfaces for generic custom authentication is tracked in #156.

Is there any way i can "fake" authentication in juno?

I'm not sure what's "fake." Protected features require authenticated users. These are saved within a system collection named #user. If you are looking to hack around, one thing you could try is handling the reading and setting of such entities yourself. You can replicate the getDoc and setDoc methods from

const user: User | undefined = await getDoc<UserData>({

This requires being able to obtain or derive an identity on your own—i.e., an identity is a must. It is worth noting that this can also potentially become insecure quickly; therefore, precautions are advised.

I know that i can make my customIdentity ...

If you are not using the built-in supported authentication (II and NFID), you have indeed have to pass the satellite parameters with your identity for each call.

Let me know if you have any other questions.

@CodingFu
Copy link
Author

This was very very helpful! I managed to implement authentication! Thank you very much.

@peterpeterparker
Copy link
Contributor

Cool! Is your solution open source? Would you ming sharing it?

@CodingFu
Copy link
Author

CodingFu commented Jun 24, 2024

let customIdentity: Identity | null = null;

export function setAPICustomIdentity(identity: Identity) {
  customIdentity = identity;
}

export function getSatteliteOptions(): SatelliteOptions {
  let options: SatelliteOptions = {};

  if (customIdentity) {
    options = {
      identity: customIdentity,
      satelliteId: import.meta.env.VITE_JUNO_ID as string,
    };
  }

  return options;
}
import React, { useEffect } from "react";
import { initCloudStorage } from "@tma.js/sdk"
import Loading from "../LoadingSplashScreen";
import { Ed25519KeyIdentity, ECDSAKeyIdentity } from "@dfinity/identity"
import { useRecoilState } from "recoil";
import { authState, customIdentity as customIdentityAtom } from "@/atoms/auth";
import { getSatteliteOptions, setAPICustomIdentity } from "@/api";
import {  fetchProfile, profileState } from "@/atoms/profile";
import { User, UserData, getDoc, setDoc } from "@junobuild/core";
import { Profile } from "@/types/entities";
import { useLocation, useNavigate } from "react-router-dom";
import { postEvent } from '@tma.js/sdk';

export default function TelegramAuthProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const IDENITITY_KEY = "telegram_internet_identity";
  const [isInitialized, setIsInitialized] = React.useState(false);
  const [, setUser] = useRecoilState<User | null>(authState);
  const [, setProfile] = useRecoilState<Profile | null>(profileState);

  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {

    async function authorize() {
      const cloudStorage = initCloudStorage();

      console.log("authorizing")
      let restoredIdentity = await cloudStorage.get(IDENITITY_KEY);

      let identity : Ed25519KeyIdentity;
      if (!restoredIdentity) {
        console.log("generating identity")
        identity = await Ed25519KeyIdentity.generate();
        await cloudStorage.set(IDENITITY_KEY, JSON.stringify(identity.toJSON()));
        console.log("idenitity saved", identity.toJSON())
        console.log("principal:", identity.getPrincipal().toString())
      } else {
        identity = Ed25519KeyIdentity.fromJSON(restoredIdentity);
        console.log("restored identity", identity)
        console.log("principal:", identity.getPrincipal().toString())
      }

      setAPICustomIdentity(identity);

      const user = await getDoc<UserData>({
        collection: `#user`,
        key: identity.getPrincipal().toText(),
        satellite: getSatteliteOptions()
      });


      if (!user) {
        const result = await setDoc<UserData>({
          collection: `#user`,
          doc: {
            key: identity.getPrincipal().toText(),
            data: {
              provider: "internet_identity",
            },
          },
          satellite: getSatteliteOptions()
        });
        setUser(result);
      } else {
        setUser(user);
      }
      setIsInitialized(true);
    }

    authorize();
  }, []);

  if (!isInitialized) {
    return <Loading />;
  }

  return <>{children}</>;
}

Then whenever I do getDoc or setDoc I do it like that:

await setDoc({
  collection: 'profiles',
  doc: {
    key: '123',
    data: { username: 'CodingFu' }
  },
  satellite: getSatteliteOptions()
})

@CodingFu
Copy link
Author

Baically i generate an identity and store it in telegram cloudStorage

it's like localStorage but it's synced between telegram clients on laptops, iphones, etc.

I'm still not 100% sure on security, but it does the trick.

I plan to move it out to auth canister and will make auth canister give me delegated identity, but while we are in prototyping phase, this approach works for me :)

@CodingFu
Copy link
Author

CodingFu commented Jun 24, 2024

@peterpeterparker

typescript nags at me, but what if I set provider to telegram in

const result = await setDoc<UserData>({
          collection: `#user`,
          doc: {
            key: identity.getPrincipal().toText(),
            data: {
              // @ts-ignore
              provider: "telegram",
            },
          },
          satellite: getSatteliteOptions()
        });

will it break the system? will satellite allow this?

@peterpeterparker
Copy link
Contributor

peterpeterparker commented Jun 25, 2024

Thanks a lot for the share, insteresting approach.

I'm still not 100% sure on security, but it does the trick.

I don't know how cloudStorage works but, indeed that does not feel super secure. Is the access to cloudStorage securised or anyone that basically knows telegram_internet_identity can retrieve the identity you stored? If the latest, it would be quite a loop hole.

I also don't know how things are encoded in cloudStorage...

typescript nags at me, but what if I set provider to telegram in

It's because typescript accepts only internet identity or nfid (see here). That will be extended once we develop the feature #156.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants