mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-09 20:00:12 +00:00
Harvey Tindall
54e4a51a7f
shared "newUser" method is now "NewUserPostVerification", and is shared between all routes which create a jellyfin account. The new "NewUserFromInvite", "NewUserFromAdmin" and "NewUserFromConfirmationKey" are smaller as a result. Discord, Telegram, and Matrix now implement the "ContactMethodLinker" and "ContactMethodUser" interfaces, meaning code is shared a lot between them in the NewUser methods, and the specifics are now in their own files. Ombi/Jellyseerr similarly implement a simpler interface "ThirdPartyService", which simply has ImportUser and AddContactMethod routes. Note these new interface methods are only used for user creation as of yet, but could likely be used in other places.
181 lines
4.9 KiB
Go
181 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
lm "github.com/hrfee/jfa-go/logmessages"
|
|
"github.com/hrfee/mediabrowser"
|
|
"github.com/lithammer/shortuuid/v3"
|
|
)
|
|
|
|
// ReponseFunc responds to the user, generally by HTTP response
|
|
// The cases when more than this occurs are given below.
|
|
type ResponseFunc func(gc *gin.Context)
|
|
|
|
// LogFunc prints a log line once called.
|
|
type LogFunc func()
|
|
|
|
type ContactMethodConf struct {
|
|
Email, Discord, Telegram, Matrix bool
|
|
}
|
|
type ContactMethodUsers struct {
|
|
Email emailStore
|
|
Discord DiscordUser
|
|
Telegram TelegramVerifiedToken
|
|
Matrix MatrixUser
|
|
}
|
|
|
|
type ContactMethodValidation struct {
|
|
Verified ContactMethodConf
|
|
Users ContactMethodUsers
|
|
}
|
|
|
|
type NewUserParams struct {
|
|
Req newUserDTO
|
|
SourceType ActivitySource
|
|
Source string
|
|
ContextForIPLogging *gin.Context
|
|
Profile *Profile
|
|
}
|
|
|
|
type NewUserData struct {
|
|
Created bool
|
|
Success bool
|
|
User mediabrowser.User
|
|
Message string
|
|
Status int
|
|
Log func()
|
|
}
|
|
|
|
// FIXME: First load of steps are going in NewUserFromInvite, because they're only used there.
|
|
// Make an interface{} for Require/Verify/ExistingUser which all contact daemons respect, then loop through!
|
|
/*
|
|
-- STEPS --
|
|
- Validate Invite
|
|
- Validate CAPTCHA
|
|
- Validate Password
|
|
- a) Discord (Require, Verify, ExistingUser, ApplyRole)
|
|
b) Telegram (Require, Verify, ExistingUser)
|
|
c) Matrix (Require, Verify, ExistingUser)
|
|
d) Email (Require, Verify, ExistingUser)
|
|
* Check for existing user
|
|
* Generate JF user
|
|
- Delete Invite
|
|
* Store Activity
|
|
* Store Email
|
|
- Store Discord/Telegram/Matrix/Label
|
|
- Notify Admin (Doesn't really matter when this happens)
|
|
* Apply Profile
|
|
* Generate JS, Ombi Users, apply profiles
|
|
* Send Welcome Email
|
|
|
|
|
|
*/
|
|
|
|
func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData) {
|
|
// Some helper functions which will behave as our app.info/error/debug
|
|
deferLogInfo := func(s string, args ...any) {
|
|
out.Log = func() {
|
|
app.info.Printf(s, args)
|
|
}
|
|
}
|
|
/* deferLogDebug := func(s string, args ...any) {
|
|
out.Log = func() {
|
|
app.debug.Printf(s, args)
|
|
}
|
|
} */
|
|
deferLogError := func(s string, args ...any) {
|
|
out.Log = func() {
|
|
app.err.Printf(s, args)
|
|
}
|
|
}
|
|
|
|
existingUser, _, _ := app.jf.UserByName(p.Req.Username, false)
|
|
if existingUser.Name != "" {
|
|
out.Message = lm.UserExists
|
|
deferLogInfo(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, out.Message)
|
|
out.Status = 401
|
|
return
|
|
}
|
|
|
|
var status int
|
|
var err error
|
|
out.User, status, err = app.jf.NewUser(p.Req.Username, p.Req.Password)
|
|
if !(status == 200 || status == 204) || err != nil {
|
|
out.Message = err.Error()
|
|
deferLogError(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, out.Message)
|
|
out.Status = 401
|
|
return
|
|
}
|
|
out.Created = true
|
|
// Invalidate Cache to be safe
|
|
app.jf.CacheExpiry = time.Now()
|
|
|
|
app.storage.SetActivityKey(shortuuid.New(), Activity{
|
|
Type: ActivityCreation,
|
|
UserID: out.User.ID,
|
|
SourceType: p.SourceType,
|
|
Source: p.Source,
|
|
InviteCode: p.Req.Code, // Left blank when an admin does this
|
|
Value: out.User.Name,
|
|
Time: time.Now(),
|
|
}, p.ContextForIPLogging, (p.SourceType != ActivityAdmin))
|
|
|
|
if p.Profile != nil {
|
|
status, err = app.jf.SetPolicy(out.User.ID, p.Profile.Policy)
|
|
if !((status == 200 || status == 204) && err == nil) {
|
|
app.err.Printf(lm.FailedApplyTemplate, "policy", lm.Jellyfin, out.User.ID, err)
|
|
}
|
|
status, err = app.jf.SetConfiguration(out.User.ID, p.Profile.Configuration)
|
|
if (status == 200 || status == 204) && err == nil {
|
|
status, err = app.jf.SetDisplayPreferences(out.User.ID, p.Profile.Displayprefs)
|
|
}
|
|
if !((status == 200 || status == 204) && err == nil) {
|
|
app.err.Printf(lm.FailedApplyTemplate, "configuration", lm.Jellyfin, out.User.ID, err)
|
|
}
|
|
|
|
for _, tps := range app.thirdPartyServices {
|
|
if !tps.Enabled(app, p.Profile) {
|
|
continue
|
|
}
|
|
// When ok and err != nil, its a non-fatal failure that we lot without the "FailedImportUser".
|
|
err, ok := tps.ImportUser(out.User.ID, p.Req, *p.Profile)
|
|
if !ok {
|
|
app.err.Printf(lm.FailedImportUser, tps.Name(), p.Req.Username, err)
|
|
} else if err != nil {
|
|
app.info.Println(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Welcome email is sent by each user of this method separately..
|
|
|
|
out.Status = 200
|
|
out.Success = true
|
|
return
|
|
}
|
|
|
|
func (app *appContext) WelcomeNewUser(user mediabrowser.User, expiry time.Time) (failed bool) {
|
|
if !app.config.Section("welcome_email").Key("enabled").MustBool(false) {
|
|
// we didn't "fail", we "politely declined"
|
|
// failed = true
|
|
return
|
|
}
|
|
failed = true
|
|
name := app.getAddressOrName(user.ID)
|
|
if name == "" {
|
|
return
|
|
}
|
|
msg, err := app.email.constructWelcome(user.Name, expiry, app, false)
|
|
if err != nil {
|
|
app.err.Printf(lm.FailedConstructWelcomeMessage, user.ID, err)
|
|
} else if err := app.sendByID(msg, user.ID); err != nil {
|
|
app.err.Printf(lm.FailedSendWelcomeMessage, user.ID, name, err)
|
|
} else {
|
|
app.info.Printf(lm.SentWelcomeMessage, user.ID, name)
|
|
failed = false
|
|
}
|
|
return
|
|
}
|