Commit f7c21cd5 authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

Add user management and local sign-in for admin console

- Introduce local user creation and management API
- Support role assignment (admin or user) during user creation
- Implement local authentication via /auth/v1/login
- Restrict local sign-in exclusively to users with the admin role
- Update meep-admin-console UI with local sign-in dialog and state handling
- Fix OAuth redirect behavior to preserve frontend application paths
parent 0439ea29
Loading
Loading
Loading
Loading
+132 −0
Original line number Diff line number Diff line
@@ -189,7 +189,139 @@ paths:
          description: "Unauthorized"
        404:
          description: "Not Found"
  /users:
    post:
      tags:
      - "users"
      summary: "Create a new user"
      description: "Create a new user. Requires admin role."
      operationId: "createUser"
      consumes:
      - "application/json"
      produces:
      - "application/json"
      parameters:
      - in: "body"
        name: "user"
        description: "User to create"
        required: true
        schema:
          $ref: "#/definitions/User"
      responses:
        201:
          description: "Created"
        400:
          description: "Bad Request"
        401:
          description: "Unauthorized"
        403:
          description: "Forbidden"
    get:
      tags:
      - "users"
      summary: "Get all users"
      description: "Get a list of all registered users. Requires admin role."
      operationId: "getUsers"
      produces:
      - "application/json"
      parameters: []
      responses:
        200:
          description: "OK"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/User"
        401:
          description: "Unauthorized"
        403:
          description: "Forbidden"
  /users/{provider}/{username}:
    delete:
      tags:
      - "users"
      summary: "Delete a user"
      description: "Delete a user. Requires admin role."
      operationId: "deleteUser"
      produces:
      - "application/json"
      parameters:
      - name: "provider"
        in: "path"
        description: "Oauth provider"
        required: true
        type: "string"
      - name: "username"
        in: "path"
        description: "Username"
        required: true
        type: "string"
      responses:
        200:
          description: "OK"
        401:
          description: "Unauthorized"
        403:
          description: "Forbidden"
        404:
          description: "Not Found"
    put:
      tags:
      - "users"
      summary: "Update user role"
      description: "Update user role. Requires admin role."
      operationId: "updateUserRole"
      consumes:
      - "application/json"
      produces:
      - "application/json"
      parameters:
      - name: "provider"
        in: "path"
        description: "Oauth provider"
        required: true
        type: "string"
      - name: "username"
        in: "path"
        description: "Username"
        required: true
        type: "string"
      - name: "role"
        in: "body"
        description: "New user role"
        required: true
        schema:
          $ref: "#/definitions/UserRole"
      responses:
        200:
          description: "OK"
        400:
          description: "Bad Request"
        401:
          description: "Unauthorized"
        403:
          description: "Forbidden"
        404:
          description: "Not Found"
definitions:
  User:
    type: "object"
    properties:
      provider:
        type: "string"
      username:
        type: "string"
      password:
        type: "string"
      role:
        type: "string"
      sboxname:
        type: "string"
  UserRole:
    type: "object"
    properties:
      role:
        type: "string"
  Sandbox:
    type: "object"
    properties:
+135 −0
Original line number Diff line number Diff line
package server

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

	users "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-users"
	"github.com/gorilla/mux"
)

type User struct {
	Provider string `json:"provider,omitempty"`
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
	Role     string `json:"role,omitempty"`
	Sboxname string `json:"sboxname,omitempty"`
}

type UserRole struct {
	Role string `json:"role,omitempty"`
}

func checkAdminRole(w http.ResponseWriter, r *http.Request) bool {
	sessionStore := authSvc.sessionMgr.GetSessionStore()
	session, err := sessionStore.Get(r)
	if err != nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return false
	}
	if session.Role != users.RoleAdmin {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return false
	}
	return true
}

func GetUsers(w http.ResponseWriter, r *http.Request) {
	if !checkAdminRole(w, r) {
		return
	}

	userMap, err := authSvc.userStore.GetUsers()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var userList []User
	for _, u := range userMap {
		userList = append(userList, User{
			Provider: u.Provider,
			Username: u.Username,
			Role:     u.Role,
			Sboxname: u.Sboxname,
		})
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(userList)
}

func UpdateUserRole(w http.ResponseWriter, r *http.Request) {
	if !checkAdminRole(w, r) {
		return
	}

	vars := mux.Vars(r)
	provider := vars["provider"]
	username := vars["username"]

	var userRole UserRole
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&userRole)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	err = authSvc.userStore.UpdateUser(provider, username, "", userRole.Role, "")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
}

func DeleteUser(w http.ResponseWriter, r *http.Request) {
	if !checkAdminRole(w, r) {
		return
	}

	vars := mux.Vars(r)
	provider := vars["provider"]
	username := vars["username"]

	err := authSvc.userStore.DeleteUser(provider, username)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
if !checkAdminRole(w, r) {

}

var user User
decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&user)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if user.Provider == "" || user.Username == "" || user.Role == "" {
		http.Error(w, "Missing required fields", http.StatusBadRequest)
		return
	}

	err = authSvc.userStore.CreateUser(user.Provider, user.Username, user.Password, user.Role, user.Sboxname)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusCreated)
}
+33 −2
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ type AuthRoute struct {
type LoginRequest struct {
	provider      string
	createSandbox string
	returnUrl     string
	timer         *time.Timer
}

@@ -248,6 +249,31 @@ func Init() (err error) {
	}
	log.Info("Connected to User Store")

	// Seed default admin user
	defaultAdminProvider := os.Getenv("MEEP_DEFAULT_ADMIN_PROVIDER")
	defaultAdminUsername := os.Getenv("MEEP_DEFAULT_ADMIN_USERNAME")
	if defaultAdminUsername != "" {
		if defaultAdminProvider == "" {
			defaultAdminProvider = "github"
		}
		_, err := authSvc.userStore.GetUser(defaultAdminProvider, defaultAdminUsername)
		if err != nil {
			err = authSvc.userStore.CreateUser(defaultAdminProvider, defaultAdminUsername, "default", users.RoleAdmin, "")
			if err != nil {
				log.Error("Failed to create default admin user: ", err.Error())
			} else {
				log.Info("Created default admin user: ", defaultAdminUsername)
			}
		} else {
			err = authSvc.userStore.UpdateUser(defaultAdminProvider, defaultAdminUsername, "", users.RoleAdmin, "")
			if err != nil {
				log.Error("Failed to update default admin user role: ", err.Error())
			} else {
				log.Info("Updated default admin user role to admin: ", defaultAdminUsername)
			}
		}
	}

	// Retrieve & cache endpoint authorization permissions
	cachePermissions()

@@ -1133,9 +1159,12 @@ func asAuthorize(w http.ResponseWriter, r *http.Request) {
	if err != nil {
		cacheBuster = ""
	}

	// Redirect user to sandbox
	redirectURL := authSvc.uri + "?sbox=" + sandboxName + "&user=" + userId + "&role=" + userRole + "&cb=" + cacheBuster
	returnUrl := "/"
	if request.returnUrl != "" {
		returnUrl = request.returnUrl
	}
	redirectURL := authSvc.uri + returnUrl + "?sbox=" + sandboxName + "&user=" + userId + "&role=" + userRole + "&cb=" + cacheBuster
	process.redirectURL = redirectURL
	process.cookies = w.Header().Values("Set-Cookie")
	close(process.done)
@@ -1163,6 +1192,7 @@ func asLogin(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	provider := query.Get("provider")
	createSandbox := query.Get("sbox")
	returnUrl := query.Get("return_url")
	metric.Provider = provider

	// Get provider-specific OAuth config
@@ -1192,6 +1222,7 @@ func asLogin(w http.ResponseWriter, r *http.Request) {
	request := &LoginRequest{
		provider:      provider,
		createSandbox: createSandbox,
		returnUrl:     returnUrl,
		timer:         time.NewTimer(10 * time.Minute),
	}
	setLoginRequest(state, request)
+28 −0
Original line number Diff line number Diff line
@@ -134,4 +134,32 @@ var routes = Routes{
		"/auth/v1/watchdog",
		TriggerWatchdog,
	},

	Route{
		"GetUsers",
		strings.ToUpper("Get"),
		"/auth/v1/users",
		GetUsers,
	},

	Route{
		"CreateUser",
		strings.ToUpper("Post"),
		"/auth/v1/users",
		CreateUser,
	},

	Route{
		"UpdateUserRole",
		strings.ToUpper("Put"),
		"/auth/v1/users/{provider}/{username}",
		UpdateUserRole,
	},

	Route{
		"DeleteUser",
		strings.ToUpper("Delete"),
		"/auth/v1/users/{provider}/{username}",
		DeleteUser,
	},
}
+7 −0
Original line number Diff line number Diff line
@@ -739,6 +739,13 @@ func deployRunScriptsAndGetFlags(targetName string, chart string, cobraCmd *cobr
		if maxSessions != "" {
			flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_MAX_SESSIONS="+maxSessions)
		}
		// Default Admin
		defaultAdminProvider := utils.RepoCfg.GetString("repo.deployment.auth.default-admin.provider")
		defaultAdminUsername := utils.RepoCfg.GetString("repo.deployment.auth.default-admin.username")
		if defaultAdminUsername != "" {
			flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_DEFAULT_ADMIN_PROVIDER="+defaultAdminProvider)
			flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_DEFAULT_ADMIN_USERNAME="+defaultAdminUsername)
		}
		// GitHub
		githubEnabled := utils.RepoCfg.GetBool("repo.deployment.auth.github.enabled")
		if githubEnabled {
Loading