Full Implementation

Below is a full implementation example that merges the Sign-In Routes using JWT and the Subname Management Routes into a single Express.js application:

import express, { Request } from 'express';
import cors from 'cors';
import { JustaName, ChainId, JustaNameConfig } from "@justaname.id/sdk";
import dotenv from 'dotenv';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';

dotenv.config();

// Extract environment variables
const chainId = parseInt(process.env.JUSTANAME_CHAIN_ID as string) as ChainId;
const domain = process.env.JUSTANAME_DOMAIN as string;
const origin = process.env.JUSTANAME_ORIGIN as string;
const apiKey = process.env.JUSTANAME_API_KEY as string;
const providerUrl = process.env.JUSTANAME_PROVIDER_URL as string;
const ensDomain = process.env.JUSTANAME_ENS_DOMAIN as string;

// JustaName configuration
export const config: JustaNameConfig = {
  apiKey,
  providerUrl,
  config: {
    chainId,
    domain,
    origin,
  },
  ensDomain,
};

const app = express();

app.use(
  cors({
    origin: true,
    credentials: true,
  })
);

app.use(express.json());
app.use(cookieParser());

let justaname: JustaName;

const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret';

// Middleware to authenticate JWT from cookie
function authenticateToken(req: Request, res, next) {
  const token = req.cookies['justverifiedtoken'];

  if (!token) {
    return res.status(401).json({ message: 'You have to first sign in' });
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ message: 'Invalid token' });
    }
    req.user = user; // Save user data in request
    next();
  });
}

// Endpoint to generate nonce for sign-in
app.get('/api/signin/nonce', async (req: Request, res) => {
  const nonce = justaname.signIn.generateNonce();
  
  const token = jwt.sign({ nonce }, JWT_SECRET, { expiresIn: '1h' });

  res.cookie('justverifiednonce', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'None',
  });

  res.status(200).send(nonce);
});

// Endpoint to handle sign-in using JWT
app.post('/api/signin', async (req: Request, res) => {
  try {
    const { message, signature } = req.body;

    const { data: messageData, ens } = await justaname.signIn.signIn({
      message,
      signature,
    });

    const payload = {
      address: messageData.address,
      ens,
      chainId: messageData.chainId,
    };

    const expiresIn = Math.floor(
      (new Date(messageData.expirationTime).getTime() - Date.now()) / 1000
    );

    const token = jwt.sign(payload, JWT_SECRET, { expiresIn });

    res.cookie('justverifiedtoken', token, {
      httpOnly: true,
      secure: true,
      sameSite: 'None',
    });

    return res.status(200).send({ ens, address: messageData.address, chainId: messageData.chainId });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

// Endpoint to retrieve current signed-in user details using JWT
app.get('/api/current', authenticateToken, (req: Request, res) => {
  res.status(200).json({
    ens: req.user.ens,
    address: req.user.address,
    chainId: req.user.chainId,
  });
});

// Endpoint to sign out and clear the JWT
app.post('/api/signout', (req, res) => {
  res.clearCookie('justverifiedtoken', {
    httpOnly: true,
    secure: true,
    sameSite: 'None',
  });
  res.status(200).json({ message: 'You have been signed out' });
});

// Subname Management Routes

interface SubnameRequest {
  username: string;
  address: string;
  ensDomain: string;
  signature: string;
  message: string;
}

// Endpoint to add a subname
app.post("/api/subnames/add", async (req: Request<SubnameRequest>, res) => {
  const { username, ensDomain, address, signature, message } = req.body;

  if (!username || !ensDomain || !address || !signature || !message) {
    return res.status(400).send({ message: "Required fields are missing" });
  }

  const existingNames = await justaname.subnames.getAllByAddress({
    address,
    isClaimed: true,
    chainId,
    coinType: 60,
  });

  if (existingNames.find((name) => name.subname.endsWith(`.${ensDomain}`))) {
    return res.status(400).send({ message: "Address already claimed" });
  }

  try {
    const result = await justaname.subnames.addSubname(
      { username, ensDomain, chainId },
      { xSignature: signature, xAddress: address, xMessage: message }
    );

    return res.status(201).send(result);
  } catch (error) {
    return res.status(500).send({ error: error instanceof Error ? error.message : "Error" });
  }
});

// Endpoint to revoke a subname
app.post("/api/subnames/revoke", async (req: Request<SubnameRequest>, res) => {
  const { username, ensDomain, address, signature, message } = req.body;

  if (!username || !ensDomain || !address || !signature || !message) {
    return res.status(400).send({ message: "Required fields are missing" });
  }

  try {
    const result = await justaname.subnames.revokeSubname(
      { username, ensDomain, chainId },
      { xSignature: signature, xAddress: address, xMessage: message }
    );

    return res.status(201).send(result);
  } catch (error) {
    return res.status(500).send({ error: error instanceof Error ? error.message : "Error" });
  }
});

// Server initialization
const port = process.env.PORT || 3333;

const server = app.listen(port, async () => {
  justaname = JustaName.init(config);
  console.log(`Listening at http://localhost:${port}/api`);
});

server.on('error', console.error);

Last updated