parcoursmob/core/application/auth.go

240 lines
5.7 KiB
Go

package application
import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"io"
"time"
"git.coopgo.io/coopgo-platform/groups-management/grpcapi"
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
ma "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/rs/zerolog/log"
)
type OAuth2CallbackResult struct {
RedirectURL string
IDToken string
}
func (h *ApplicationHandler) ProcessOAuth2Callback(code string, redirectSession string) (*OAuth2CallbackResult, error) {
oauth2Token, err := h.idp.OAuth2Config.Exchange(context.Background(), code)
if err != nil {
log.Error().Err(err).Msg("Exchange error")
return nil, err
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Error().Msg("Cannot retrieve ID token")
return nil, errors.New("cannot retrieve ID token")
}
_, err = h.idp.TokenVerifier.Verify(context.Background(), rawIDToken)
if err != nil {
log.Error().Err(err).Msg("Not able to verify token")
return nil, err
}
redirect := "/app/"
if redirectSession != "" {
redirect = redirectSession
}
return &OAuth2CallbackResult{
RedirectURL: redirect,
IDToken: rawIDToken,
}, nil
}
type LostPasswordInitResult struct {
Success bool
}
func (h *ApplicationHandler) InitiateLostPassword(email string) (*LostPasswordInitResult, error) {
account, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &mobilityaccounts.GetAccountUsernameRequest{
Username: email,
Namespace: "parcoursmob",
})
if err != nil {
return nil, err
}
b := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return nil, err
}
key := base64.RawURLEncoding.EncodeToString(b)
passwordretrieval := map[string]any{
"username": email,
"account_id": account.Account.Id,
"key": key,
}
h.cache.PutWithTTL("retrieve-password/"+key, passwordretrieval, 72*time.Hour)
if err := h.emailing.Send("auth.retrieve_password", email, passwordretrieval); err != nil {
return nil, err
}
return &LostPasswordInitResult{Success: true}, nil
}
type LostPasswordRecoverResult struct {
Success bool
}
func (h *ApplicationHandler) RecoverLostPassword(key, newPassword string) (*LostPasswordRecoverResult, error) {
recover, err := h.cache.Get("retrieve-password/" + key)
if err != nil {
return nil, err
}
if newPassword == "" {
return nil, errors.New("password is empty")
}
_, err = h.services.GRPC.MobilityAccounts.ChangePassword(context.TODO(), &mobilityaccounts.ChangePasswordRequest{
Id: recover.(map[string]any)["account_id"].(string),
Password: newPassword,
})
if err != nil {
return nil, err
}
err = h.cache.Delete("retrieve-password/" + key)
if err != nil {
log.Error().Err(err).Msg("Failed to delete password recovery key")
}
return &LostPasswordRecoverResult{Success: true}, nil
}
func (h *ApplicationHandler) GetPasswordRecoveryData(key string) (map[string]any, error) {
recover, err := h.cache.Get("retrieve-password/" + key)
if err != nil {
return nil, err
}
return recover.(map[string]any), nil
}
type OnboardingResult struct {
Success bool
}
func (h *ApplicationHandler) CompleteOnboarding(key, password, firstName, lastName string) (*OnboardingResult, error) {
onboarding, err := h.cache.Get("onboarding/" + key)
if err != nil {
return nil, err
}
onboardingmap := onboarding.(map[string]any)
if password == "" {
return nil, errors.New("password is empty")
}
groups := []string{
onboardingmap["group"].(string),
}
if onboardingmap["admin"].(bool) {
groups = append(groups, onboardingmap["group"].(string)+":admin")
}
display_name := firstName + " " + lastName
account := &ma.Account{
Authentication: ma.AccountAuth{
Local: ma.LocalAuth{
Username: onboardingmap["username"].(string),
Password: password,
},
},
Namespace: "parcoursmob",
Data: map[string]any{
"display_name": display_name,
"first_name": firstName,
"last_name": lastName,
"email": onboardingmap["username"],
"groups": groups,
},
}
acc, err := mobilityaccounts.AccountFromStorageType(account)
if err != nil {
return nil, err
}
request := &mobilityaccounts.RegisterRequest{
Account: acc,
}
_, err = h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)
if err != nil {
return nil, err
}
err = h.cache.Delete("onboarding/" + key)
if err != nil {
log.Error().Err(err).Msg("Failed to delete onboarding key")
}
return &OnboardingResult{Success: true}, nil
}
func (h *ApplicationHandler) GetOnboardingData(key string) (map[string]any, error) {
onboarding, err := h.cache.Get("onboarding/" + key)
if err != nil {
return nil, err
}
return onboarding.(map[string]any), nil
}
type UserGroupsResult struct {
Groups []groupsstorage.Group
}
func (h *ApplicationHandler) GetUserGroups(idtoken *oidc.IDToken) (*UserGroupsResult, error) {
var claims map[string]any
err := idtoken.Claims(&claims)
if err != nil {
return nil, err
}
g := claims["groups"]
groups_interface, ok := g.([]any)
if !ok {
return nil, errors.New("invalid groups format")
}
groups := []string{}
for _, v := range groups_interface {
groups = append(groups, v.(string))
}
request := &grpcapi.GetGroupsBatchRequest{
Groupids: groups,
}
resp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), request)
if err != nil {
return nil, err
}
var groupsresponse []groupsstorage.Group
for _, group := range resp.Groups {
if group.Namespace != "parcoursmob_organizations" {
continue
}
g := group.ToStorageType()
groupsresponse = append(groupsresponse, g)
}
return &UserGroupsResult{Groups: groupsresponse}, nil
}