Respect RegistrationOIDC.RequireInvite

This previously setting had no effect when clicking 'Sign in with
$PROVIDER' on the home page.
This commit is contained in:
Evan Goode 2025-08-23 20:34:20 -04:00
parent 1c53e8c2dd
commit bb524367eb
3 changed files with 85 additions and 84 deletions

View File

@ -560,11 +560,11 @@ func (app *App) getIDTokenCookie(c *echo.Context) (*OIDCProvider, string, oidc.I
func FrontCompleteRegistration(app *App) func(c echo.Context) error {
type completeRegistrationContext struct {
baseContext
User *User
InviteCode string
AnyUnmigratedUsers bool
AllowChoosingPlayerName bool
PreferredPlayerName string
User *User
InviteCode string
OIDCProvider *OIDCProvider
AnyUnmigratedUsers bool
PreferredPlayerName string
}
returnURL := Unwrap(url.JoinPath(app.FrontEndURL, "web/registration"))
@ -600,12 +600,12 @@ func FrontCompleteRegistration(app *App) func(c echo.Context) error {
}
return c.Render(http.StatusOK, "complete-registration", completeRegistrationContext{
baseContext: app.NewBaseContext(&c),
User: user,
InviteCode: inviteCode,
PreferredPlayerName: preferredPlayerName,
AllowChoosingPlayerName: provider.Config.AllowChoosingPlayerName,
AnyUnmigratedUsers: anyUnmigratedUsers,
baseContext: app.NewBaseContext(&c),
User: user,
InviteCode: inviteCode,
PreferredPlayerName: preferredPlayerName,
OIDCProvider: provider,
AnyUnmigratedUsers: anyUnmigratedUsers,
})
})
}

55
user.go
View File

@ -95,6 +95,21 @@ func (app *App) CreateUser(
}
}
var invite mo.Option[Invite]
if inviteCode != nil {
var inviteStruct Invite
result := app.DB.First(&inviteStruct, "code = ?", *inviteCode)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return User{}, InviteNotFoundError
} else {
return User{}, result.Error
}
} else {
invite = mo.Some(inviteStruct)
}
}
oidcIdentities := make([]UserOIDCIdentity, 0, len(oidcIdentitySpecs.Value))
for _, oidcIdentitySpec := range oidcIdentitySpecs.Value {
provider, ok := app.OIDCProvidersByIssuer[oidcIdentitySpec.Issuer]
@ -104,6 +119,9 @@ func (app *App) CreateUser(
if oidcIdentitySpec.Subject == "" {
return User{}, NewBadRequestUserError("OIDC subject for provider %s can't be blank.", provider.Config.Issuer)
}
if !callerIsAdmin && invite.IsAbsent() && provider.Config.RequireInvite {
return User{}, InviteMissingError
}
oidcIdentities = append(oidcIdentities, UserOIDCIdentity{
UserUUID: userUUID,
Issuer: provider.Config.Issuer,
@ -125,26 +143,6 @@ func (app *App) CreateUser(
return User{}, NewBadRequestUserError("Invalid preferred language.")
}
getInvite := func(requireInvite bool) (*Invite, error) {
var invite Invite
if inviteCode == nil {
if requireInvite && !callerIsAdmin {
return nil, InviteMissingError
}
return nil, nil
} else {
result := app.DB.First(&invite, "code = ?", *inviteCode)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, InviteNotFoundError
}
return nil, result.Error
}
return &invite, nil
}
}
var invite *Invite
var playerUUID string
if existingPlayer {
// Existing player registration
@ -152,10 +150,8 @@ func (app *App) CreateUser(
return User{}, NewBadRequestUserError("Registration from an existing player is not allowed.")
}
var err error
invite, err = getInvite(app.Config.RegistrationExistingPlayer.RequireInvite)
if err != nil {
return User{}, err
if !callerIsAdmin && invite.IsAbsent() && app.Config.RegistrationExistingPlayer.RequireInvite {
return User{}, InviteMissingError
}
if err := app.ValidatePlayerName(*playerName); err != nil {
@ -182,13 +178,12 @@ func (app *App) CreateUser(
return User{}, NewBadRequestUserError("Registration without some existing player is not allowed.")
}
var err error
invite, err = getInvite(app.Config.RegistrationNewPlayer.RequireInvite)
if err != nil {
return User{}, err
if !callerIsAdmin && invite.IsAbsent() && app.Config.RegistrationNewPlayer.RequireInvite {
return User{}, InviteMissingError
}
if chosenUUID == nil {
var err error
playerUUID, err = app.NewPlayerUUID(*playerName)
if err != nil {
return User{}, err
@ -332,8 +327,8 @@ func (app *App) CreateUser(
}
}
if invite != nil {
if err := tx.Delete(invite).Error; err != nil {
if i, ok := invite.Get(); ok {
if err := tx.Delete(i).Error; err != nil {
return User{}, err
}
}

View File

@ -16,7 +16,7 @@
{{ end }}
<h3>{{ call .T "Migrate an existing user" }}</h3>
<p>{{ call .T "You can link this identity provider to an existing %s account." }} <span class="warning-message">{{ call .T "If you do so, you will no longer be able to log in using your %s password. You'll need to use your Minecraft Token to log in to Minecraft launchers." }}</span></p>
<p>{{ call .T "You can link this identity provider to an existing %s account." .App.Config.ApplicationName }} <span class="warning-message">{{ call .T "If you do so, you will no longer be able to log in using your %s password. You'll need to use your Minecraft Token to log in to Minecraft launchers." .App.Config.ApplicationName }}</span></p>
<form action="{{ .App.FrontEndURL }}/web/oidc-migrate" method="post">
<input type="text" name="username" placeholder="{{ call .T "Username" }}" required />
@ -29,7 +29,7 @@
/>
<input type="submit" value="{{ call .T "Link account" }}" />
</form>
{{ $dividerNeeded := true }}
{{ $dividerNeeded = true }}
{{ end }}
<!-- CreateNewPlayer -->
@ -39,46 +39,52 @@
{{ $dividerNeeded = false }}
{{ end }}
<h3>{{ call .T "Create a player" }}</h3>
<p>{{ call .T "Complete registration by creating a new player:" }}</p>
<form action="{{ .App.FrontEndURL }}/web/register" method="post">
<input
required
type="text"
name="playerName"
placeholder="{{ call .T "Player name" }}"
maxlength="{{ .App.Constants.MaxUsernameLength }}"
value="{{ .PreferredPlayerName }}"
{{ if not .AllowChoosingPlayerName }}
title="{{ call .T "Choosing a player name is not allowed." }}"
disabled
{{ if and .OIDCProvider.Config.RequireInvite (not .InviteCode) }}
<p>{{ call .T "Registration with %s requires an invite." .OIDCProvider.Config.Name }}</p>
{{ else if and .App.Config.RegistrationExistingPlayer.RequireInvite (not .InviteCode) }}
<p>{{ call .T "Registration as a new player is invite-only." }}</p>
{{ else }}
<p>{{ call .T "Complete registration by creating a new player:" }}</p>
<form action="{{ .App.FrontEndURL }}/web/register" method="post">
<input
required
type="text"
name="playerName"
placeholder="{{ call .T "Player name" }}"
maxlength="{{ .App.Constants.MaxUsernameLength }}"
value="{{ .PreferredPlayerName }}"
{{ if not .OIDCProvider.Config.AllowChoosingPlayerName }}
title="{{ call .T "Choosing a player name is not allowed." }}"
disabled
{{ end }}
/>
<input
hidden
type="checkbox"
name="useIdToken"
checked
/>
{{ if .App.Config.CreateNewPlayer.AllowChoosingUUID }}
<p>
<input
class="long"
type="text"
name="uuid"
placeholder="{{ if eq .App.Config.PlayerUUIDGeneration "offline" }}{{ call .T "Player UUID (leave blank for offline UUID)" }}{{ else }}{{ call .T "Player UUID (leave blank for random)" }}{{ end }}"
pattern="^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$"
/>
</p>
{{ end }}
<input type="text" name="inviteCode" value="{{ .InviteCode }}" hidden />
<input hidden name="returnUrl" value="{{ .URL }}" />
{{ if .InviteCode }}
<p><em>{{ call .T "Using invite code %s" .InviteCode }}</em></p>
{{ end }}
/>
<input
hidden
type="checkbox"
name="useIdToken"
checked
/>
{{ if .App.Config.CreateNewPlayer.AllowChoosingUUID }}
<p>
<input
class="long"
type="text"
name="uuid"
placeholder="{{ if eq .App.Config.PlayerUUIDGeneration "offline" }}{{ call .T "Player UUID (leave blank for offline UUID)" }}{{ else }}{{ call .T "Player UUID (leave blank for random)" }}{{ end }}"
pattern="^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$"
/>
<input type="submit" value="{{ call .T "Register" }}"/>
</p>
{{ end }}
<input type="text" name="inviteCode" value="{{ .InviteCode }}" hidden />
<input hidden name="returnUrl" value="{{ .URL }}" />
{{ if .InviteCode }}
<p><em>{{ call .T "Using invite code %s" .InviteCode }}</em></p>
{{ end }}
<p>
<input type="submit" value="{{ call .T "Register" }}"/>
</p>
</form>
</form>
{{ end }}
{{ $dividerNeeded = true }}
{{ end }}
@ -89,9 +95,9 @@
{{ $dividerNeeded = false }}
{{ end }}
<h3>{{ call .T "Register from an existing Minecraft player" }}</h3>
{{ if and .App.Config.RegistrationExistingPlayer.RequireInvite (not
.InviteCode)
}}
{{ if and .OIDCProvider.Config.RequireInvite (not .InviteCode) }}
<p>{{ call .T "Registration with %s requires an invite." .OIDCProvider.Config.Name }}</p>
{{ else if and .App.Config.RegistrationExistingPlayer.RequireInvite (not .InviteCode) }}
<p>{{ call .T "Registration as an existing player is invite-only." }}</p>
{{ else }}
{{ if .App.Config.ImportExistingPlayer.RequireSkinVerification }}
@ -111,7 +117,7 @@
name="playerName"
placeholder="{{ call .T "%s player name" .App.Config.ImportExistingPlayer.Nickname }}"
maxlength="{{ .App.Constants.MaxUsernameLength }}"
{{ if not .AllowChoosingPlayerName }}
{{ if not .OIDCProvider.Config.AllowChoosingPlayerName }}
value="{{ .PreferredPlayerName }}"
title="{{ call .T "Choosing a player name is not allowed." }}"
disabled