Compare commits

...

17 Commits

Author SHA1 Message Date
jmiller 3fc609149c chore: sync updates.xml 05.14.00 from main [skip ci] 2026-05-31 03:48:08 +00:00
jmiller a44e16834d chore: sync updates.xml 05.13.00 from main [skip ci] 2026-05-31 03:38:49 +00:00
jmiller 57f188b505 chore: sync updates.xml 05.12.00 from main [skip ci] 2026-05-31 03:34:25 +00:00
jmiller d0412716b3 chore: sync updates.xml 05.11.00 from main [skip ci] 2026-05-31 03:19:25 +00:00
jmiller 491e8b547e chore: sync updates.xml 05.10.00 from main [skip ci] 2026-05-31 03:08:16 +00:00
jmiller 418626aac2 chore: sync updates.xml 05.09.00 from main [skip ci] 2026-05-31 02:49:53 +00:00
jmiller 092b44bbc1 chore: sync updates.xml 05.05.00 from main [skip ci] 2026-05-30 18:02:43 +00:00
jmiller 446cca3534 chore: sync updates.xml 05.04.00 from main [skip ci] 2026-05-30 17:35:29 +00:00
jmiller 60c92317fc chore: sync updates.xml 05.03.00 from main [skip ci] 2026-05-30 17:26:05 +00:00
jmiller e0c25b4b1e chore: sync updates.xml 05.02.00 from main [skip ci] 2026-05-30 16:13:43 +00:00
Giteabot 0f23219ee4 fix: http content file render (#37850) (#37856)
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Backport #37850

Fix #37849

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
2026-05-26 12:54:37 -05:00
jmiller bc475c91f6 Merge pull request 'feat: login notification via email and ntfy' (#209) from feat/login-notifications into dev
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 21s
2026-05-26 16:40:51 +00:00
Jonathan Miller 25268d7dd7 feat: login notification via email and ntfy on successful sign-in
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
When a user signs in, sends notifications with username, IP address,
user agent, and timestamp. Notifications go through:
- Email to the user's registered address
- ntfy push to the configured topic

Enabled by default, configurable via app.ini:
  [login_notification]
  ENABLED = true

The notification fires asynchronously (goroutine) so it doesn't
block the login redirect. Hooks into handleSignInFull which is the
single choke point for all auth methods (password, 2FA, OAuth).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 11:39:44 -05:00
jmiller c7193abc0c Merge pull request 'fix: help link in footer, login logo on signin page' (#205) from fix/help-footer-login-logo into dev
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 22s
2026-05-26 04:52:00 +00:00
Jonathan Miller e6a4dfccf0 fix(ui): add help link to footer, show login logo on signin page
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
- Help link added to footer right-links (next to API and Licenses)
- Login logo (login-logo.png) now shown on the signin page, not just
  the home page. Hidden via onerror when not uploaded.
- Landing page is set to 'login' so home.tmpl never renders — the
  logo needed to be on signin_inner.tmpl instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:51:37 -05:00
jmiller a5c805b0f6 Merge pull request 'fix(ui): show help link for all users' (#204) from fix/help-link-always-visible into dev
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 24s
2026-05-26 04:45:56 +00:00
Jonathan Miller 46ce0a7e32 fix(ui): show help link in navbar for all users, not just anonymous
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
The help link was wrapped in {{if not .IsSigned}} which hid it for
logged-in users. Removed the condition so it's always visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:45:49 -05:00
9 changed files with 191 additions and 67 deletions
+14 -2
View File
@@ -63,7 +63,7 @@ func TestFile(t *testing.T) {
{
name: "tags.py",
code: "<>",
want: lines(`<span class="o">&lt;</span><span class="o">&gt;</span>`),
want: lines(`<span class="o">&lt;&gt;</span>`),
lexerName: "Python",
},
{
@@ -102,7 +102,7 @@ c=2
<span class="n">def</span><span class="p">:</span>\n
<span class="n">a</span><span class="o">=</span><span class="mi">1</span>\n
\n
<span class="n">b</span><span class="o">=</span><span class="sa"></span><span class="s1">&#39;</span><span class="s1">&#39;</span>\n
<span class="n">b</span><span class="o">=</span><span class="s1">&#39;&#39;</span>\n
\n
<span class="n">c</span><span class="o">=</span><span class="mi">2</span>`,
),
@@ -114,6 +114,18 @@ c=2
want: []template.HTML{"<span class=\"c1\">--\n</span>", `<span class="k">SELECT</span>`},
lexerName: "SQL",
},
{
name: "test.http",
code: `HTTP/1.0 400 Bad request
Content-Type: text/html
<html></html>`,
want: lines(`<span class="kr">HTTP</span><span class="o">/</span><span class="m">1.0</span> <span class="m">400</span> <span class="ne">Bad request</span>\n
<span class="n">Content-Type</span><span class="o">:</span> <span class="l">text/html</span>\n
\n
<span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>`),
lexerName: "HTTP",
},
}
for _, tt := range tests {
+4 -4
View File
@@ -288,24 +288,24 @@ func detectChromaLexerWithAnalyze(fileName, lang string, code []byte) chroma.Lex
// if lang is provided, and it matches a lexer, use it directly
if byLang {
return lexer
return chroma.Coalesce(lexer)
}
// if a lexer is detected and there is no conflict for the file extension, use it directly
fileExt := path.Ext(fileName)
_, hasConflicts := chromaLexers().conflictingExtLangMap[fileExt]
if !hasConflicts && lexer != lexers.Fallback {
return lexer
return chroma.Coalesce(lexer)
}
// try to detect language by content, for best guessing for the language
// when using "code" to detect, analyze.GetCodeLanguage is slow, it iterates many rules to detect language from content
analyzedLanguage := analyze.GetCodeLanguage(fileName, code)
lexer = DetectChromaLexerByFileName(fileName, analyzedLanguage)
lexer, _ = detectChromaLexerByFileName(fileName, analyzedLanguage)
if lexer == lexers.Fallback {
if analyzedLanguage != enry.OtherLanguage {
log.Warn("No chroma lexer found for enry detected language: %s (file: %s), need to fix the language mapping between enry and chroma.", analyzedLanguage, fileName)
}
}
return lexer
return chroma.Coalesce(lexer)
}
+13
View File
@@ -39,6 +39,13 @@ var (
Channel: "stable",
}
// LoginNotification configuration for sign-in alerts
LoginNotification = struct {
Enabled bool
}{
Enabled: true,
}
// IsInTesting indicates whether the testing is running (unit test or integration test). It can be used for:
// * Skip nonsense error logs during testing caused by unreliable code (TODO: this is only a temporary solution, we should make the test code more reliable)
// * Panic in dev or testing mode to make the problem more obvious and easier to debug
@@ -171,6 +178,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
loadOtherFrom(cfg)
loadUpdateCheckerFrom(cfg)
loadNtfyFrom(cfg)
loadLoginNotificationFrom(cfg)
return nil
}
@@ -181,6 +189,11 @@ func loadUpdateCheckerFrom(cfg ConfigProvider) {
UpdateChecker.Channel = sec.Key("CHANNEL").MustString(UpdateChecker.Channel)
}
func loadLoginNotificationFrom(cfg ConfigProvider) {
sec := cfg.Section("login_notification")
LoginNotification.Enabled = sec.Key("ENABLED").MustBool(true)
}
func loadRunModeFrom(rootCfg ConfigProvider) {
rootSec := rootCfg.Section("")
RunUser = rootSec.Key("RUN_USER").MustString(user.CurrentUsername())
+3
View File
@@ -440,6 +440,9 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool) {
ctx.ServerError("UpdateUser", err)
return
}
// Send login notification (email + ntfy)
go mailer.SendLoginNotification(u, ctx.RemoteAddr(), ctx.Req.UserAgent())
}
// extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user.
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package mailer
import (
"bytes"
"fmt"
"io"
"net/http"
"time"
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
sender_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/mailer/sender"
)
// SendLoginNotification sends email and ntfy notifications when a user signs in.
func SendLoginNotification(u *user_model.User, ip, userAgent string) {
if !setting.LoginNotification.Enabled {
return
}
timestamp := time.Now().UTC().Format("2006-01-02 15:04:05 UTC")
subject := fmt.Sprintf("[%s] New sign-in: %s", setting.AppName, u.Name)
body := fmt.Sprintf(`New sign-in detected
Account: %s (%s)
IP Address: %s
Browser: %s
Time: %s
Instance: %s
If this wasn't you, change your password immediately and review your active sessions.
— %s`, u.Name, u.Email, ip, userAgent, timestamp, setting.AppURL, setting.AppName)
// Email notification
if setting.MailService != nil && u.Email != "" {
msg := sender_service.NewMessage(u.EmailTo(), subject, body)
msg.Info = fmt.Sprintf("Login notification for %s", u.Name)
SendAsync(msg)
log.Debug("Login notification email sent to %s", u.Email)
}
// ntfy push notification
if setting.Ntfy.Enabled && setting.Ntfy.ServerURL != "" {
go sendLoginNtfy(subject, u.Name, ip, timestamp)
}
}
func sendLoginNtfy(title, username, ip, timestamp string) {
body := fmt.Sprintf("User: %s\nIP: %s\nTime: %s", username, ip, timestamp)
url := fmt.Sprintf("%s/%s", setting.Ntfy.ServerURL, setting.Ntfy.DefaultTopic)
req, err := http.NewRequest("POST", url, bytes.NewBufferString(body))
if err != nil {
log.Error("ntfy login: create request: %v", err)
return
}
req.Header.Set("Title", title)
req.Header.Set("Priority", "default")
req.Header.Set("Tags", "key,login")
req.Header.Set("Click", setting.AppURL+"-/admin")
if setting.Ntfy.Token != "" {
req.Header.Set("Authorization", "Bearer "+setting.Ntfy.Token)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
log.Error("ntfy login: send: %v", err)
return
}
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body)
if resp.StatusCode >= 300 {
log.Error("ntfy login: status %d", resp.StatusCode)
}
}
+1
View File
@@ -36,6 +36,7 @@
</div>
<a href="{{AssetUrlPrefix}}/licenses.txt">{{ctx.Locale.Tr "licenses"}}</a>
{{if .EnableSwagger}}<a href="{{AppSubUrl}}/api/swagger">API</a>{{end}}
<a href="{{HelpURL}}" target="_blank">{{ctx.Locale.Tr "help"}}</a>
{{template "custom/extra_links_footer" .}}
</div>
</footer>
+1 -3
View File
@@ -35,9 +35,7 @@
{{template "custom/extra_links" .}}
{{if not .IsSigned}}
<a class="item" target="_blank" href="{{HelpURL}}">{{ctx.Locale.Tr "help"}}</a>
{{end}}
<a class="item" target="_blank" href="{{HelpURL}}">{{ctx.Locale.Tr "help"}}</a>
</div>
<!-- the full dropdown menus -->
+3
View File
@@ -1,4 +1,7 @@
<div class="ui container fluid">
<div class="tw-text-center tw-mb-4">
<img src="{{AssetUrlPrefix}}/img/login-logo.png" style="max-width: 220px; max-height: 80px; object-fit: contain;" onerror="this.style.display='none'">
</div>
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
{{template "base/alert" .}}
{{end}}
+68 -58
View File
@@ -1,93 +1,103 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 05.01.02
VERSION: 05.14.00
-->
<updates>
<update>
<name>MokoGitea</name>
<description>MokoGitea update</description>
<description>MokoGitea dev build.</description>
<element>mokogitea</element>
<type>application</type>
<version>05.01.02</version>
<client>server</client>
<tags><tag>stable</tag></tags>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.01.02</infourl>
<client>site</client>
<version>05.05.00-dev</version>
<creationDate>2026-05-30</creationDate>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/development</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.01.02</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/development/mokogitea-05.05.00-dev.zip</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
<tags><tag>dev</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="go" version=".*"/>
</update>
<update>
<name>MokoGitea</name>
<description>MokoGitea update</description>
<description>MokoGitea alpha build.</description>
<element>mokogitea</element>
<type>application</type>
<version>05.01.02</version>
<client>server</client>
<tags><tag>rc</tag></tags>
<infourl title="MokoGitea RC">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.01.02</infourl>
<client>site</client>
<version>05.05.00-alpha</version>
<creationDate>2026-05-30</creationDate>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/alpha</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.01.02</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/alpha/mokogitea-05.05.00-alpha.zip</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>MokoGitea</name>
<description>MokoGitea update</description>
<element>mokogitea</element>
<type>application</type>
<version>05.00.00</version>
<client>server</client>
<tags><tag>beta</tag></tags>
<infourl title="MokoGitea Beta">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.00.00</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.00.00</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<update>
<name>MokoGitea</name>
<description>MokoGitea update</description>
<element>mokogitea</element>
<type>application</type>
<version>05.00.00</version>
<client>server</client>
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
<tags><tag>alpha</tag></tags>
<infourl title="MokoGitea Alpha">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/v1.26.1-moko.05.00.00</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.05.00.00</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="go" version=".*"/>
</update>
<update>
<name>MokoGitea</name>
<description>MokoGitea update</description>
<description>MokoGitea beta build.</description>
<element>mokogitea</element>
<type>application</type>
<version>06.00.00-dev</version>
<client>server</client>
<tags><tag>development</tag></tags>
<infourl title="MokoGitea Dev">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/src/branch/dev</infourl>
<client>site</client>
<version>05.05.00-beta</version>
<creationDate>2026-05-30</creationDate>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/beta</infourl>
<downloads>
<downloadurl type="full" format="docker">git.mokoconsulting.tech/mokoconsulting/mokogitea:v1.26.1-moko.06.00.00-dev</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/beta/mokogitea-05.05.00-beta.zip</downloadurl>
</downloads>
<sha256></sha256>
<targetplatform name="mokogitea" version="((1\.25\.)|(1\.26\.))" />
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
<tags><tag>beta</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="go" version=".*"/>
</update>
<update>
<name>MokoGitea</name>
<description>MokoGitea rc build.</description>
<element>mokogitea</element>
<type>application</type>
<client>site</client>
<version>05.05.00-rc</version>
<creationDate>2026-05-30</creationDate>
<infourl title="MokoGitea">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/release-candidate</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/release-candidate/mokogitea-05.05.00-rc.zip</downloadurl>
</downloads>
<sha256>4fee9eb03e4b819a63bce2ceb54fdce0d3eb8bf5b31460fcc42e5ecd75cc856e</sha256>
<tags><tag>rc</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="go" version=".*"/>
</update>
<update>
<name>MokoGitea</name>
<description>MokoGitea stable build.</description>
<element>mokogitea</element>
<type>application</type>
<client>site</client>
<version>05.14.00</version>
<creationDate>2026-05-31</creationDate>
<infourl title='MokoGitea'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/stable</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/stable/mokogitea-05.14.00.zip</downloadurl>
</downloads>
<sha256>bec4bf5a1a841f8e72d9826451004db5d8afc70144231dfedc7fb01a6695955c</sha256>
<tags><tag>stable</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="go" version=".*" />
</update>
</updates>