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 { func FrontCompleteRegistration(app *App) func(c echo.Context) error {
type completeRegistrationContext struct { type completeRegistrationContext struct {
baseContext baseContext
User *User User *User
InviteCode string InviteCode string
AnyUnmigratedUsers bool OIDCProvider *OIDCProvider
AllowChoosingPlayerName bool AnyUnmigratedUsers bool
PreferredPlayerName string PreferredPlayerName string
} }
returnURL := Unwrap(url.JoinPath(app.FrontEndURL, "web/registration")) 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{ return c.Render(http.StatusOK, "complete-registration", completeRegistrationContext{
baseContext: app.NewBaseContext(&c), baseContext: app.NewBaseContext(&c),
User: user, User: user,
InviteCode: inviteCode, InviteCode: inviteCode,
PreferredPlayerName: preferredPlayerName, PreferredPlayerName: preferredPlayerName,
AllowChoosingPlayerName: provider.Config.AllowChoosingPlayerName, OIDCProvider: provider,
AnyUnmigratedUsers: anyUnmigratedUsers, 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)) oidcIdentities := make([]UserOIDCIdentity, 0, len(oidcIdentitySpecs.Value))
for _, oidcIdentitySpec := range oidcIdentitySpecs.Value { for _, oidcIdentitySpec := range oidcIdentitySpecs.Value {
provider, ok := app.OIDCProvidersByIssuer[oidcIdentitySpec.Issuer] provider, ok := app.OIDCProvidersByIssuer[oidcIdentitySpec.Issuer]
@ -104,6 +119,9 @@ func (app *App) CreateUser(
if oidcIdentitySpec.Subject == "" { if oidcIdentitySpec.Subject == "" {
return User{}, NewBadRequestUserError("OIDC subject for provider %s can't be blank.", provider.Config.Issuer) 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{ oidcIdentities = append(oidcIdentities, UserOIDCIdentity{
UserUUID: userUUID, UserUUID: userUUID,
Issuer: provider.Config.Issuer, Issuer: provider.Config.Issuer,
@ -125,26 +143,6 @@ func (app *App) CreateUser(
return User{}, NewBadRequestUserError("Invalid preferred language.") 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 var playerUUID string
if existingPlayer { if existingPlayer {
// Existing player registration // Existing player registration
@ -152,10 +150,8 @@ func (app *App) CreateUser(
return User{}, NewBadRequestUserError("Registration from an existing player is not allowed.") return User{}, NewBadRequestUserError("Registration from an existing player is not allowed.")
} }
var err error if !callerIsAdmin && invite.IsAbsent() && app.Config.RegistrationExistingPlayer.RequireInvite {
invite, err = getInvite(app.Config.RegistrationExistingPlayer.RequireInvite) return User{}, InviteMissingError
if err != nil {
return User{}, err
} }
if err := app.ValidatePlayerName(*playerName); err != nil { 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.") return User{}, NewBadRequestUserError("Registration without some existing player is not allowed.")
} }
var err error if !callerIsAdmin && invite.IsAbsent() && app.Config.RegistrationNewPlayer.RequireInvite {
invite, err = getInvite(app.Config.RegistrationNewPlayer.RequireInvite) return User{}, InviteMissingError
if err != nil {
return User{}, err
} }
if chosenUUID == nil { if chosenUUID == nil {
var err error
playerUUID, err = app.NewPlayerUUID(*playerName) playerUUID, err = app.NewPlayerUUID(*playerName)
if err != nil { if err != nil {
return User{}, err return User{}, err
@ -332,8 +327,8 @@ func (app *App) CreateUser(
} }
} }
if invite != nil { if i, ok := invite.Get(); ok {
if err := tx.Delete(invite).Error; err != nil { if err := tx.Delete(i).Error; err != nil {
return User{}, err return User{}, err
} }
} }

View File

@ -16,7 +16,7 @@
{{ end }} {{ end }}
<h3>{{ call .T "Migrate an existing user" }}</h3> <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"> <form action="{{ .App.FrontEndURL }}/web/oidc-migrate" method="post">
<input type="text" name="username" placeholder="{{ call .T "Username" }}" required /> <input type="text" name="username" placeholder="{{ call .T "Username" }}" required />
@ -29,7 +29,7 @@
/> />
<input type="submit" value="{{ call .T "Link account" }}" /> <input type="submit" value="{{ call .T "Link account" }}" />
</form> </form>
{{ $dividerNeeded := true }} {{ $dividerNeeded = true }}
{{ end }} {{ end }}
<!-- CreateNewPlayer --> <!-- CreateNewPlayer -->
@ -39,46 +39,52 @@
{{ $dividerNeeded = false }} {{ $dividerNeeded = false }}
{{ end }} {{ end }}
<h3>{{ call .T "Create a player" }}</h3> <h3>{{ call .T "Create a player" }}</h3>
<p>{{ call .T "Complete registration by creating a new player:" }}</p> {{ if and .OIDCProvider.Config.RequireInvite (not .InviteCode) }}
<form action="{{ .App.FrontEndURL }}/web/register" method="post"> <p>{{ call .T "Registration with %s requires an invite." .OIDCProvider.Config.Name }}</p>
<input {{ else if and .App.Config.RegistrationExistingPlayer.RequireInvite (not .InviteCode) }}
required <p>{{ call .T "Registration as a new player is invite-only." }}</p>
type="text" {{ else }}
name="playerName" <p>{{ call .T "Complete registration by creating a new player:" }}</p>
placeholder="{{ call .T "Player name" }}" <form action="{{ .App.FrontEndURL }}/web/register" method="post">
maxlength="{{ .App.Constants.MaxUsernameLength }}" <input
value="{{ .PreferredPlayerName }}" required
{{ if not .AllowChoosingPlayerName }} type="text"
title="{{ call .T "Choosing a player name is not allowed." }}" name="playerName"
disabled 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 }} {{ end }}
/>
<input
hidden
type="checkbox"
name="useIdToken"
checked
/>
{{ if .App.Config.CreateNewPlayer.AllowChoosingUUID }}
<p> <p>
<input <input type="submit" value="{{ call .T "Register" }}"/>
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> </p>
{{ end }} </form>
<input type="text" name="inviteCode" value="{{ .InviteCode }}" hidden /> {{ end }}
<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>
{{ $dividerNeeded = true }} {{ $dividerNeeded = true }}
{{ end }} {{ end }}
@ -89,9 +95,9 @@
{{ $dividerNeeded = false }} {{ $dividerNeeded = false }}
{{ end }} {{ end }}
<h3>{{ call .T "Register from an existing Minecraft player" }}</h3> <h3>{{ call .T "Register from an existing Minecraft player" }}</h3>
{{ if and .App.Config.RegistrationExistingPlayer.RequireInvite (not {{ if and .OIDCProvider.Config.RequireInvite (not .InviteCode) }}
.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> <p>{{ call .T "Registration as an existing player is invite-only." }}</p>
{{ else }} {{ else }}
{{ if .App.Config.ImportExistingPlayer.RequireSkinVerification }} {{ if .App.Config.ImportExistingPlayer.RequireSkinVerification }}
@ -111,7 +117,7 @@
name="playerName" name="playerName"
placeholder="{{ call .T "%s player name" .App.Config.ImportExistingPlayer.Nickname }}" placeholder="{{ call .T "%s player name" .App.Config.ImportExistingPlayer.Nickname }}"
maxlength="{{ .App.Constants.MaxUsernameLength }}" maxlength="{{ .App.Constants.MaxUsernameLength }}"
{{ if not .AllowChoosingPlayerName }} {{ if not .OIDCProvider.Config.AllowChoosingPlayerName }}
value="{{ .PreferredPlayerName }}" value="{{ .PreferredPlayerName }}"
title="{{ call .T "Choosing a player name is not allowed." }}" title="{{ call .T "Choosing a player name is not allowed." }}"
disabled disabled