# Authentication Example In Code

# Introduction

In the examples below, we demonstrate how to implement the Armada authentication flow in different frameworks and languages. Each example follows a similar structure:

  1. An install endpoint that receives the app_id and xcode, decrypts the xcode, and redirects to the Armada verification URL.
  2. A callback endpoint that receives the installation data, validates the xcode, and stores the access_token securely.

# NodeJS - Express

import express from 'express';
import cache from './cache.js'; // Assuming a cache module (like Redis)
import { decryptXcode } from './utils.js'; // Assuming decryption utility
import authMiddleware from './authMiddleware.js'; // Middleware to authenticate the user
import db from './db.js'; // Database utility

const app = express();
app.use(express.json());

// Step 1: Handle Install URL with Authentication Middleware
app.get('/install', authMiddleware, (req, res) => {
  const { app_id, xcode } = req.query;
  
  if (!app_id || !xcode) {
    return res.status(400).send('Missing required parameters');
  }
  
  if (app_id != process.env.ARMADA_APP_ID) {
	return res.status(400).send('Invalid app id');
  }

  let code;
  try {
    code = decryptXcode(xcode, process.env.ARMADA_APP_SECRET);
  } catch (error) {
    return res.status(400).send('Decryption failed');
  }

  // Store xcode, app_id, and your user info in cache (TTL: 5 minutes = 300 seconds)
  cache.set(xcode, { 
    app_id,
    code, 
    user: req.user // Your authenticated user from middleware
  }, 300);
  
  // Redirect to Armada verification
  res.redirect(`https://api.armadadelivery.com/integrations/apps/install/verify?xcode=${xcode}&code=${code}`);
});

// Step 2: Handle Callback
app.post('/callback', async (req, res) => {
  const { xcode, app_data, user_data, access_token } = req.body;
  
  // Validate xcode exists in cache
  const stored = await cache.get(xcode);
  if (!stored) {
    return res.status(400).send('Invalid xcode');
  }

  const { user } = stored; // Retrieve your user from cache

  // Store access_token securely with the user reference
  const armadaUserID = user_data.reference_id; 
  await db.saveArmadaAppToken(armadaUserID, access_token, user);
  
  // Commit changes
  res.status(200).send('OK');
});

export default app;

# Python - Django

from django.http import HttpResponse, HttpResponseRedirect
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from .utils import decrypt_xcode
from .models import ArmadaAppToken
from django.core.cache import cache
import json

@require_http_methods(["GET"])
def install(request):
    app_id = request.GET.get('app_id')
    xcode = request.GET.get('xcode')

    if not app_id or not xcode:
        return HttpResponse('Missing required parameters', status=400)

    if app_id != settings.ARMADA_APP_ID:
        return HttpResponse('Invalid app id', status=400)

    try:
        code = decrypt_xcode(xcode, settings.ARMADA_APP_SECRET)
    except Exception:
        return HttpResponse('Decryption failed', status=400)

    # Store xcode, app_id, and user info in cache (TTL: 5 minutes)
    cache.set(xcode, {
        'app_id': app_id,
        'code': code,
        'user': request.user.id  # Assuming user authentication
    }, timeout=300)

    return HttpResponseRedirect(f'https://api.armadadelivery.com/integrations/apps/install/verify?xcode={xcode}&code={code}')

@csrf_exempt
@require_http_methods(["POST"])
def callback(request):
    data = json.loads(request.body)
    xcode = data.get('xcode')
    app_data = data.get('app_data')
    user_data = data.get('user_data')
    access_token = data.get('access_token')

    stored = cache.get(xcode)
    if not stored:
        return HttpResponse('Invalid xcode', status=400)

    user_id = stored['user']

    # Store access_token securely with the user reference
    armada_user_id = user_data['reference_id']
    ArmadaAppToken.objects.create(
        armada_user_id=armada_user_id,
        access_token=access_token,
        user_id=user_id
    )

    return HttpResponse('OK')

# PHP - Laravel

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redirect;
use App\Models\ArmadaAppToken;
use App\Utils\XcodeDecryptor;

class ArmadaAuthController extends Controller
{
    public function install(Request $request)
    {
        $appId = $request->query('app_id');
        $xcode = $request->query('xcode');

        if (!$appId || !$xcode) {
            return response('Missing required parameters', 400);
        }

        if ($appId != config('armada.app_id')) {
            return response('Invalid app id', 400);
        }

        try {
            $code = XcodeDecryptor::decrypt($xcode, config('armada.app_secret'));
        } catch (\Exception $e) {
            return response('Decryption failed', 400);
        }

        // Store xcode, app_id, and user info in cache (TTL: 5 minutes)
        Cache::put($xcode, [
            'app_id' => $appId,
            'code' => $code,
            'user' => auth()->id()  // Assuming user authentication
        ], 300);

        return Redirect::to("https://api.armadadelivery.com/integrations/apps/install/verify?xcode={$xcode}&code={$code}");
    }

    public function callback(Request $request)
    {
        $data = $request->json()->all();
        $xcode = $data['xcode'];
        $appData = $data['app_data'];
        $userData = $data['user_data'];
        $accessToken = $data['access_token'];

        $stored = Cache::get($xcode);
        if (!$stored) {
            return response('Invalid xcode', 400);
        }

        $userId = $stored['user'];

        // Store access_token securely with the user reference
        $armadaUserId = $userData['reference_id'];
        ArmadaAppToken::create([
            'armada_user_id' => $armadaUserId,
            'access_token' => $accessToken,
            'user_id' => $userId
        ]);

        return response('OK');
    }
}

# Ruby on Rails

# app/controllers/armada_auth_controller.rb
class ArmadaAuthController < ApplicationController
  def install
    app_id = params[:app_id]
    xcode = params[:xcode]

    if app_id.blank? || xcode.blank?
      return render plain: 'Missing required parameters', status: :bad_request
    end

    if app_id != ENV['ARMADA_APP_ID']
      return render plain: 'Invalid app id', status: :bad_request
    end

    begin
      code = XcodeDecryptor.decrypt(xcode, ENV['ARMADA_APP_SECRET'])
    rescue StandardError
      return render plain: 'Decryption failed', status: :bad_request
    end

    # Store xcode, app_id, and user info in cache (TTL: 5 minutes)
    Rails.cache.write(xcode, {
      app_id: app_id,
      code: code,
      user: current_user.id  # Assuming user authentication
    }, expires_in: 5.minutes)

    redirect_to "https://api.armadadelivery.com/integrations/apps/install/verify?xcode=#{xcode}&code=#{code}"
  end

  def callback
    data = JSON.parse(request.body.read)
    xcode = data['xcode']
    app_data = data['app_data']
    user_data = data['user_data']
    access_token = data['access_token']

    stored = Rails.cache.read(xcode)
    return render plain: 'Invalid xcode', status: :bad_request if stored.nil?

    user_id = stored[:user]

    # Store access_token securely with the user reference
    armada_user_id = user_data['reference_id']
    ArmadaAppToken.create!(
      armada_user_id: armada_user_id,
      access_token: access_token,
      user_id: user_id
    )

    render plain: 'OK'
  end
end

# Go - Echo

package main

import (
	"encoding/json"
	"net/http"
	"os"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/go-redis/redis/v8"
)

var (
	rdb *redis.Client
)

func main() {
	e := echo.New()
	
	rdb = redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	e.GET("/install", handleInstall)
	e.POST("/callback", handleCallback)

	e.Logger.Fatal(e.Start(":8080"))
}

func handleInstall(c echo.Context) error {
	appID := c.QueryParam("app_id")
	xcode := c.QueryParam("xcode")

	if appID == "" || xcode == "" {
		return c.String(http.StatusBadRequest, "Missing required parameters")
	}

	if appID != os.Getenv("ARMADA_APP_ID") {
		return c.String(http.StatusBadRequest, "Invalid app id")
	}

	code, err := decryptXcode(xcode, os.Getenv("ARMADA_APP_SECRET"))
	if err != nil {
		return c.String(http.StatusBadRequest, "Decryption failed")
	}

	// Store xcode, app_id, and user info in cache (TTL: 5 minutes)
	err = rdb.Set(c.Request().Context(), xcode, map[string]interface{}{
		"app_id": appID,
		"code":   code,
		"user":   c.Get("user"), // Assuming user authentication middleware
	}, 5*time.Minute).Err()
	if err != nil {
		return c.String(http.StatusInternalServerError, "Failed to store data")
	}

	return c.Redirect(http.StatusFound, "https://api.armadadelivery.com/integrations/apps/install/verify?xcode="+xcode+"&code="+code)
}

func handleCallback(c echo.Context) error {
	var data struct {
		Xcode       string                 `json:"xcode"`
		AppData     map[string]interface{} `json:"app_data"`
		UserData    map[string]interface{} `json:"user_data"`
		AccessToken string                 `json:"access_token"`
	}

	if err := c.Bind(&data); err != nil {
		return c.String(http.StatusBadRequest, "Invalid request body")
	}

	stored, err := rdb.Get(c.Request().Context(), data.Xcode).Result()
	if err != nil {
		return c.String(http.StatusBadRequest, "Invalid xcode")
	}

	var storedData map[string]interface{}
	if err := json.Unmarshal([]byte(stored), &storedData); err != nil {
		return c.String(http.StatusInternalServerError, "Failed to parse stored data")
	}

	userID := storedData["user"]

	// Store access_token securely with the user reference
	armadaUserID := data.UserData["reference_id"].(string)
	// Implement your database logic here to save the token
	// For example: db.SaveArmadaAppToken(armadaUserID, data.AccessToken, userID)

	return c.String(http.StatusOK, "OK")
}