diff --git a/pkg/routes/api/shared/info.go b/pkg/routes/api/shared/info.go
new file mode 100644
index 000000000..423aae2c7
--- /dev/null
+++ b/pkg/routes/api/shared/info.go
@@ -0,0 +1,164 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package shared
+
+import (
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/license"
+ "code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/modules/auth/openid"
+ csvmigrator "code.vikunja.io/api/pkg/modules/migration/csv"
+ microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
+ "code.vikunja.io/api/pkg/modules/migration/ticktick"
+ "code.vikunja.io/api/pkg/modules/migration/todoist"
+ "code.vikunja.io/api/pkg/modules/migration/trello"
+ vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
+ "code.vikunja.io/api/pkg/modules/migration/wekan"
+ "code.vikunja.io/api/pkg/version"
+)
+
+// VikunjaInfos holds public information about this Vikunja instance.
+type VikunjaInfos struct {
+ Version string `json:"version" doc:"The Vikunja version this instance runs."`
+ FrontendURL string `json:"frontend_url" doc:"The publicly configured frontend URL of this instance."`
+ Motd string `json:"motd" doc:"The message of the day, shown to all users."`
+ LinkSharingEnabled bool `json:"link_sharing_enabled" doc:"Whether sharing projects via public links is enabled."`
+ MaxFileSize string `json:"max_file_size" doc:"The maximum allowed upload size, as a human-readable string (e.g. 20MB)."`
+ MaxItemsPerPage int `json:"max_items_per_page" doc:"The maximum number of items a paginated endpoint returns per page."`
+ AvailableMigrators []string `json:"available_migrators" doc:"The migrators enabled on this instance."`
+ TaskAttachmentsEnabled bool `json:"task_attachments_enabled" doc:"Whether task attachments are enabled."`
+ EnabledBackgroundProviders []string `json:"enabled_background_providers" doc:"The project-background providers enabled on this instance (e.g. upload, unsplash)."`
+ TotpEnabled bool `json:"totp_enabled" doc:"Whether TOTP two-factor authentication is enabled."`
+ Legal LegalInfo `json:"legal" doc:"Links to the instance's legal documents."`
+ CaldavEnabled bool `json:"caldav_enabled" doc:"Whether the CalDAV interface is enabled."`
+ AuthInfo AuthInfo `json:"auth" doc:"The authentication methods enabled on this instance."`
+ EmailRemindersEnabled bool `json:"email_reminders_enabled" doc:"Whether email reminders are enabled."`
+ UserDeletionEnabled bool `json:"user_deletion_enabled" doc:"Whether users may delete their own account."`
+ TaskCommentsEnabled bool `json:"task_comments_enabled" doc:"Whether task comments are enabled."`
+ DemoModeEnabled bool `json:"demo_mode_enabled" doc:"Whether this instance runs in demo mode (data is periodically reset)."`
+ WebhooksEnabled bool `json:"webhooks_enabled" doc:"Whether webhooks are enabled."`
+ PublicTeamsEnabled bool `json:"public_teams_enabled" doc:"Whether public teams are enabled."`
+ AllowIconChanges bool `json:"allow_icon_changes" doc:"Whether users may change project icons."`
+ EnabledProFeatures []license.Feature `json:"enabled_pro_features" doc:"The licensed pro features enabled on this instance."`
+}
+
+// AuthInfo describes the authentication methods enabled on this instance.
+type AuthInfo struct {
+ Local LocalAuthInfo `json:"local"`
+ Ldap LdapAuthInfo `json:"ldap"`
+ OpenIDConnect OpenIDAuthInfo `json:"openid_connect"`
+}
+
+// LocalAuthInfo describes the local (username/password) authentication method.
+type LocalAuthInfo struct {
+ Enabled bool `json:"enabled"`
+ RegistrationEnabled bool `json:"registration_enabled"`
+}
+
+// LdapAuthInfo describes the LDAP authentication method.
+type LdapAuthInfo struct {
+ Enabled bool `json:"enabled"`
+}
+
+// OpenIDAuthInfo describes the OpenID Connect authentication method.
+type OpenIDAuthInfo struct {
+ Enabled bool `json:"enabled"`
+ Providers []*openid.Provider `json:"providers"`
+}
+
+// LegalInfo holds links to the instance's legal documents.
+type LegalInfo struct {
+ ImprintURL string `json:"imprint_url"`
+ PrivacyPolicyURL string `json:"privacy_policy_url"`
+}
+
+// BuildInfo assembles the public instance information returned by GET /info on
+// both API versions.
+func BuildInfo() VikunjaInfos {
+ info := VikunjaInfos{
+ Version: version.Version,
+ FrontendURL: config.ServicePublicURL.GetString(),
+ Motd: config.ServiceMotd.GetString(),
+ LinkSharingEnabled: config.ServiceEnableLinkSharing.GetBool(),
+ MaxFileSize: config.FilesMaxSize.GetString(),
+ MaxItemsPerPage: config.ServiceMaxItemsPerPage.GetInt(),
+ TaskAttachmentsEnabled: config.ServiceEnableTaskAttachments.GetBool(),
+ TotpEnabled: config.ServiceEnableTotp.GetBool(),
+ CaldavEnabled: config.ServiceEnableCaldav.GetBool(),
+ EmailRemindersEnabled: config.ServiceEnableEmailReminders.GetBool(),
+ UserDeletionEnabled: config.ServiceEnableUserDeletion.GetBool(),
+ TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(),
+ DemoModeEnabled: config.ServiceDemoMode.GetBool(),
+ WebhooksEnabled: config.WebhooksEnabled.GetBool(),
+ PublicTeamsEnabled: config.ServiceEnablePublicTeams.GetBool(),
+ AllowIconChanges: config.ServiceAllowIconChanges.GetBool(),
+ EnabledProFeatures: license.EnabledProFeatures(),
+ AvailableMigrators: []string{
+ (&vikunja_file.FileMigrator{}).Name(),
+ (&ticktick.Migrator{}).Name(),
+ (&wekan.Migrator{}).Name(),
+ (&csvmigrator.Migrator{}).Name(),
+ },
+ Legal: LegalInfo{
+ ImprintURL: config.LegalImprintURL.GetString(),
+ PrivacyPolicyURL: config.LegalPrivacyURL.GetString(),
+ },
+ AuthInfo: AuthInfo{
+ Local: LocalAuthInfo{
+ Enabled: config.AuthLocalEnabled.GetBool(),
+ RegistrationEnabled: config.AuthLocalEnabled.GetBool() && config.ServiceEnableRegistration.GetBool(),
+ },
+ Ldap: LdapAuthInfo{
+ Enabled: config.AuthLdapEnabled.GetBool(),
+ },
+ OpenIDConnect: OpenIDAuthInfo{
+ Enabled: config.AuthOpenIDEnabled.GetBool(),
+ },
+ },
+ }
+
+ providers, err := openid.GetAllProviders()
+ if err != nil {
+ log.Errorf("Error while getting openid providers for /info: %s", err)
+ // No return here to not break /info
+ }
+ info.AuthInfo.OpenIDConnect.Providers = providers
+
+ if config.MigrationTodoistEnable.GetBool() {
+ m := &todoist.Migration{}
+ info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
+ }
+ if config.MigrationTrelloEnable.GetBool() {
+ m := &trello.Migration{}
+ info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
+ }
+ if config.MigrationMicrosoftTodoEnable.GetBool() {
+ m := µsofttodo.Migration{}
+ info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
+ }
+
+ if config.BackgroundsEnabled.GetBool() {
+ if config.BackgroundsUploadEnabled.GetBool() {
+ info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "upload")
+ }
+ if config.BackgroundsUnsplashEnabled.GetBool() {
+ info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "unsplash")
+ }
+ }
+
+ return info
+}
diff --git a/pkg/routes/api/v1/info.go b/pkg/routes/api/v1/info.go
index 0e0a64ff2..87891ff15 100644
--- a/pkg/routes/api/v1/info.go
+++ b/pkg/routes/api/v1/info.go
@@ -19,151 +19,18 @@ package v1
import (
"net/http"
- "code.vikunja.io/api/pkg/config"
- "code.vikunja.io/api/pkg/license"
- "code.vikunja.io/api/pkg/log"
- "code.vikunja.io/api/pkg/modules/auth/openid"
- csvmigrator "code.vikunja.io/api/pkg/modules/migration/csv"
- microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
- "code.vikunja.io/api/pkg/modules/migration/ticktick"
- "code.vikunja.io/api/pkg/modules/migration/todoist"
- "code.vikunja.io/api/pkg/modules/migration/trello"
- vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
- "code.vikunja.io/api/pkg/modules/migration/wekan"
- "code.vikunja.io/api/pkg/version"
+ "code.vikunja.io/api/pkg/routes/api/shared"
"github.com/labstack/echo/v5"
)
-type vikunjaInfos struct {
- Version string `json:"version"`
- FrontendURL string `json:"frontend_url"`
- Motd string `json:"motd"`
- LinkSharingEnabled bool `json:"link_sharing_enabled"`
- MaxFileSize string `json:"max_file_size"`
- MaxItemsPerPage int `json:"max_items_per_page"`
- AvailableMigrators []string `json:"available_migrators"`
- TaskAttachmentsEnabled bool `json:"task_attachments_enabled"`
- EnabledBackgroundProviders []string `json:"enabled_background_providers"`
- TotpEnabled bool `json:"totp_enabled"`
- Legal legalInfo `json:"legal"`
- CaldavEnabled bool `json:"caldav_enabled"`
- AuthInfo authInfo `json:"auth"`
- EmailRemindersEnabled bool `json:"email_reminders_enabled"`
- UserDeletionEnabled bool `json:"user_deletion_enabled"`
- TaskCommentsEnabled bool `json:"task_comments_enabled"`
- DemoModeEnabled bool `json:"demo_mode_enabled"`
- WebhooksEnabled bool `json:"webhooks_enabled"`
- PublicTeamsEnabled bool `json:"public_teams_enabled"`
- AllowIconChanges bool `json:"allow_icon_changes"`
- EnabledProFeatures []license.Feature `json:"enabled_pro_features"`
-}
-
-type authInfo struct {
- Local localAuthInfo `json:"local"`
- Ldap ldapAuthInfo `json:"ldap"`
- OpenIDConnect openIDAuthInfo `json:"openid_connect"`
-}
-
-type localAuthInfo struct {
- Enabled bool `json:"enabled"`
- RegistrationEnabled bool `json:"registration_enabled"`
-}
-
-type ldapAuthInfo struct {
- Enabled bool `json:"enabled"`
-}
-
-type openIDAuthInfo struct {
- Enabled bool `json:"enabled"`
- Providers []*openid.Provider `json:"providers"`
-}
-
-type legalInfo struct {
- ImprintURL string `json:"imprint_url"`
- PrivacyPolicyURL string `json:"privacy_policy_url"`
-}
-
// Info is the handler to get infos about this vikunja instance
// @Summary Info
// @Description Returns the version, frontendurl, motd and various settings of Vikunja
// @tags service
// @Produce json
-// @Success 200 {object} v1.vikunjaInfos
+// @Success 200 {object} shared.VikunjaInfos
// @Router /info [get]
func Info(c *echo.Context) error {
- info := vikunjaInfos{
- Version: version.Version,
- FrontendURL: config.ServicePublicURL.GetString(),
- Motd: config.ServiceMotd.GetString(),
- LinkSharingEnabled: config.ServiceEnableLinkSharing.GetBool(),
- MaxFileSize: config.FilesMaxSize.GetString(),
- MaxItemsPerPage: config.ServiceMaxItemsPerPage.GetInt(),
- TaskAttachmentsEnabled: config.ServiceEnableTaskAttachments.GetBool(),
- TotpEnabled: config.ServiceEnableTotp.GetBool(),
- CaldavEnabled: config.ServiceEnableCaldav.GetBool(),
- EmailRemindersEnabled: config.ServiceEnableEmailReminders.GetBool(),
- UserDeletionEnabled: config.ServiceEnableUserDeletion.GetBool(),
- TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(),
- DemoModeEnabled: config.ServiceDemoMode.GetBool(),
- WebhooksEnabled: config.WebhooksEnabled.GetBool(),
- PublicTeamsEnabled: config.ServiceEnablePublicTeams.GetBool(),
- AllowIconChanges: config.ServiceAllowIconChanges.GetBool(),
- EnabledProFeatures: license.EnabledProFeatures(),
- AvailableMigrators: []string{
- (&vikunja_file.FileMigrator{}).Name(),
- (&ticktick.Migrator{}).Name(),
- (&wekan.Migrator{}).Name(),
- (&csvmigrator.Migrator{}).Name(),
- },
- Legal: legalInfo{
- ImprintURL: config.LegalImprintURL.GetString(),
- PrivacyPolicyURL: config.LegalPrivacyURL.GetString(),
- },
- AuthInfo: authInfo{
- Local: localAuthInfo{
- Enabled: config.AuthLocalEnabled.GetBool(),
- RegistrationEnabled: config.AuthLocalEnabled.GetBool() && config.ServiceEnableRegistration.GetBool(),
- },
- Ldap: ldapAuthInfo{
- Enabled: config.AuthLdapEnabled.GetBool(),
- },
- OpenIDConnect: openIDAuthInfo{
- Enabled: config.AuthOpenIDEnabled.GetBool(),
- },
- },
- }
-
- providers, err := openid.GetAllProviders()
- if err != nil {
- log.Errorf("Error while getting openid providers for /info: %s", err)
- // No return here to not break /info
- }
-
- info.AuthInfo.OpenIDConnect.Providers = providers
-
- // Migrators
- if config.MigrationTodoistEnable.GetBool() {
- m := &todoist.Migration{}
- info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
- }
- if config.MigrationTrelloEnable.GetBool() {
- m := &trello.Migration{}
- info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
- }
- if config.MigrationMicrosoftTodoEnable.GetBool() {
- m := µsofttodo.Migration{}
- info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
- }
-
- if config.BackgroundsEnabled.GetBool() {
- if config.BackgroundsUploadEnabled.GetBool() {
- info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "upload")
- }
- if config.BackgroundsUnsplashEnabled.GetBool() {
- info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "unsplash")
- }
- }
-
- return c.JSON(http.StatusOK, info)
+ return c.JSON(http.StatusOK, shared.BuildInfo())
}
diff --git a/pkg/routes/api/v2/info.go b/pkg/routes/api/v2/info.go
new file mode 100644
index 000000000..483abf027
--- /dev/null
+++ b/pkg/routes/api/v2/info.go
@@ -0,0 +1,51 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/routes/api/shared"
+
+ "github.com/danielgtaylor/huma/v2"
+)
+
+type infoBody struct {
+ Body shared.VikunjaInfos
+}
+
+// RegisterInfoRoutes wires the public instance-info endpoint onto the Huma API.
+func RegisterInfoRoutes(api huma.API) {
+ Register(api, huma.Operation{
+ OperationID: "info",
+ Summary: "Instance info",
+ Description: "Returns version, frontend URL, motd and the enabled features of this Vikunja instance. Public — no authentication required.",
+ Method: http.MethodGet,
+ Path: "/info",
+ Tags: []string{"service"},
+ // Public: opt out of the globally-applied auth. The path is also listed
+ // in unauthenticatedAPIPaths so the token middleware lets it through.
+ Security: []map[string][]string{},
+ }, info)
+}
+
+func init() { AddRouteRegistrar(RegisterInfoRoutes) }
+
+func info(_ context.Context, _ *struct{}) (*infoBody, error) {
+ return &infoBody{Body: shared.BuildInfo()}, nil
+}
diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go
index 159994724..949180b64 100644
--- a/pkg/routes/routes.go
+++ b/pkg/routes/routes.go
@@ -343,6 +343,7 @@ var unauthenticatedAPIPaths = map[string]bool{
"/api/v2/docs": true,
"/api/v2/docs/scalar.standalone.js": true,
"/api/v2/schemas/:schema": true,
+ "/api/v2/info": true,
}
// collectRoutesForAPITokens collects all routes for API token permission checking.
diff --git a/pkg/webtests/huma_backgrounds_misc_test.go b/pkg/webtests/huma_backgrounds_misc_test.go
index 8efdc3c2b..57a87413d 100644
--- a/pkg/webtests/huma_backgrounds_misc_test.go
+++ b/pkg/webtests/huma_backgrounds_misc_test.go
@@ -17,6 +17,7 @@
package webtests
import (
+ "encoding/json"
"net/http"
"testing"
@@ -29,6 +30,22 @@ import (
"github.com/stretchr/testify/require"
)
+// TestHumaInfo covers the public instance-info endpoint. It needs no auth and
+// always reports the running version.
+func TestHumaInfo(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/info", "", "", "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+
+ var body map[string]any
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
+ assert.Contains(t, body, "version")
+ assert.Contains(t, body, "auth")
+ assert.Contains(t, body, "available_migrators")
+}
+
// TestHumaProjectBackgroundDelete covers removing a project background. It
// mirrors the v1 background_test.go matrix: the owner clears the background
// (and keeps the title), a read-only user is refused.