@weibaohui/dsh-git-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/client/bundle.js +2356 -0
- package/client/index.js +2342 -0
- package/cordis.patch.yml +11 -0
- package/package.json +70 -0
- package/server/dist/api/v1.js +2029 -0
- package/server/dist/authx/password.js +43 -0
- package/server/dist/authx/um.js +157 -0
- package/server/dist/conf.js +323 -0
- package/server/dist/context.js +701 -0
- package/server/dist/db/actions.js +202 -0
- package/server/dist/db/db.js +838 -0
- package/server/dist/dshapi.js +1416 -0
- package/server/dist/editorconfig.js +104 -0
- package/server/dist/gitx/git.js +580 -0
- package/server/dist/gitx/http.js +232 -0
- package/server/dist/gitx/paths.js +28 -0
- package/server/dist/gitx/service.js +251 -0
- package/server/dist/gosprintf.js +167 -0
- package/server/dist/hook.js +53 -0
- package/server/dist/index.js +112 -0
- package/server/dist/lfsx.js +265 -0
- package/server/dist/mailer.js +127 -0
- package/server/dist/markup.js +317 -0
- package/server/dist/metrics.js +18 -0
- package/server/dist/mirror.js +203 -0
- package/server/dist/multipart.js +33 -0
- package/server/dist/repox.js +99 -0
- package/server/dist/router.js +185 -0
- package/server/dist/serv.js +43 -0
- package/server/dist/server.js +382 -0
- package/server/dist/sshx/dispatch.js +160 -0
- package/server/dist/sshx/lfs-transfer.js +289 -0
- package/server/dist/sshx/server.js +107 -0
- package/server/dist/toolx.js +108 -0
- package/server/dist/twofactor.js +160 -0
- package/server/dist/webapi.js +377 -0
- package/server/dist/webhook.js +128 -0
- package/server/package.json +34 -0
- package/server/public/img/avatar_default.png +0 -0
- package/server/tsconfig.json +19 -0
- package/server/vendored-conf/app.ini +538 -0
- package/server/vendored-conf/gitignore/Actionscript +11 -0
- package/server/vendored-conf/gitignore/Ada +5 -0
- package/server/vendored-conf/gitignore/Agda +1 -0
- package/server/vendored-conf/gitignore/Android +32 -0
- package/server/vendored-conf/gitignore/Anjuta +3 -0
- package/server/vendored-conf/gitignore/AppEngine +2 -0
- package/server/vendored-conf/gitignore/AppceleratorTitanium +3 -0
- package/server/vendored-conf/gitignore/ArchLinuxPackages +13 -0
- package/server/vendored-conf/gitignore/Archives +27 -0
- package/server/vendored-conf/gitignore/Autotools +14 -0
- package/server/vendored-conf/gitignore/BricxCC +4 -0
- package/server/vendored-conf/gitignore/C +32 -0
- package/server/vendored-conf/gitignore/C Sharp +108 -0
- package/server/vendored-conf/gitignore/C++ +28 -0
- package/server/vendored-conf/gitignore/CFWheels +12 -0
- package/server/vendored-conf/gitignore/CMake +6 -0
- package/server/vendored-conf/gitignore/CUDA +6 -0
- package/server/vendored-conf/gitignore/CakePHP +13 -0
- package/server/vendored-conf/gitignore/ChefCookbook +9 -0
- package/server/vendored-conf/gitignore/Cloud9 +3 -0
- package/server/vendored-conf/gitignore/CodeIgniter +6 -0
- package/server/vendored-conf/gitignore/CodeKit +3 -0
- package/server/vendored-conf/gitignore/CommonLisp +3 -0
- package/server/vendored-conf/gitignore/Composer +6 -0
- package/server/vendored-conf/gitignore/Concrete5 +3 -0
- package/server/vendored-conf/gitignore/Coq +3 -0
- package/server/vendored-conf/gitignore/CraftCMS +3 -0
- package/server/vendored-conf/gitignore/DM +5 -0
- package/server/vendored-conf/gitignore/Dart +15 -0
- package/server/vendored-conf/gitignore/DartEditor +2 -0
- package/server/vendored-conf/gitignore/Delphi +57 -0
- package/server/vendored-conf/gitignore/Dreamweaver +3 -0
- package/server/vendored-conf/gitignore/Drupal +36 -0
- package/server/vendored-conf/gitignore/EPiServer +4 -0
- package/server/vendored-conf/gitignore/Eagle +36 -0
- package/server/vendored-conf/gitignore/Eclipse +39 -0
- package/server/vendored-conf/gitignore/EiffelStudio +2 -0
- package/server/vendored-conf/gitignore/Elisp +5 -0
- package/server/vendored-conf/gitignore/Elixir +4 -0
- package/server/vendored-conf/gitignore/Emacs +32 -0
- package/server/vendored-conf/gitignore/Ensime +4 -0
- package/server/vendored-conf/gitignore/Erlang +10 -0
- package/server/vendored-conf/gitignore/Espresso +1 -0
- package/server/vendored-conf/gitignore/ExpressionEngine +19 -0
- package/server/vendored-conf/gitignore/ExtJs +4 -0
- package/server/vendored-conf/gitignore/Fancy +2 -0
- package/server/vendored-conf/gitignore/Finale +13 -0
- package/server/vendored-conf/gitignore/FlexBuilder +3 -0
- package/server/vendored-conf/gitignore/ForceDotCom +4 -0
- package/server/vendored-conf/gitignore/FuelPHP +2 -0
- package/server/vendored-conf/gitignore/GWT +28 -0
- package/server/vendored-conf/gitignore/Gcov +5 -0
- package/server/vendored-conf/gitignore/GitBook +16 -0
- package/server/vendored-conf/gitignore/Go +24 -0
- package/server/vendored-conf/gitignore/Gradle +8 -0
- package/server/vendored-conf/gitignore/Grails +33 -0
- package/server/vendored-conf/gitignore/Haskell +16 -0
- package/server/vendored-conf/gitignore/IGORPro +5 -0
- package/server/vendored-conf/gitignore/IPythonNotebook +2 -0
- package/server/vendored-conf/gitignore/Idris +2 -0
- package/server/vendored-conf/gitignore/JDeveloper +13 -0
- package/server/vendored-conf/gitignore/Java +12 -0
- package/server/vendored-conf/gitignore/Jboss +19 -0
- package/server/vendored-conf/gitignore/Jekyll +3 -0
- package/server/vendored-conf/gitignore/JetBrains +46 -0
- package/server/vendored-conf/gitignore/Joomla +525 -0
- package/server/vendored-conf/gitignore/KDevelop4 +2 -0
- package/server/vendored-conf/gitignore/Kate +3 -0
- package/server/vendored-conf/gitignore/KiCAD +13 -0
- package/server/vendored-conf/gitignore/Kohana +2 -0
- package/server/vendored-conf/gitignore/LabVIEW +16 -0
- package/server/vendored-conf/gitignore/Laravel +4 -0
- package/server/vendored-conf/gitignore/Lazarus +30 -0
- package/server/vendored-conf/gitignore/Leiningen +12 -0
- package/server/vendored-conf/gitignore/LemonStand +21 -0
- package/server/vendored-conf/gitignore/LibreOffice +2 -0
- package/server/vendored-conf/gitignore/Lilypond +6 -0
- package/server/vendored-conf/gitignore/Linux +7 -0
- package/server/vendored-conf/gitignore/Lithium +2 -0
- package/server/vendored-conf/gitignore/Lua +41 -0
- package/server/vendored-conf/gitignore/LyX +4 -0
- package/server/vendored-conf/gitignore/Magento +126 -0
- package/server/vendored-conf/gitignore/Matlab +16 -0
- package/server/vendored-conf/gitignore/Maven +9 -0
- package/server/vendored-conf/gitignore/Mercurial +6 -0
- package/server/vendored-conf/gitignore/Mercury +13 -0
- package/server/vendored-conf/gitignore/MetaProgrammingSystem +16 -0
- package/server/vendored-conf/gitignore/MicrosoftOffice +10 -0
- package/server/vendored-conf/gitignore/ModelSim +23 -0
- package/server/vendored-conf/gitignore/Momentics +8 -0
- package/server/vendored-conf/gitignore/MonoDevelop +8 -0
- package/server/vendored-conf/gitignore/Nanoc +10 -0
- package/server/vendored-conf/gitignore/NetBeans +8 -0
- package/server/vendored-conf/gitignore/Nim +1 -0
- package/server/vendored-conf/gitignore/Ninja +2 -0
- package/server/vendored-conf/gitignore/Node +28 -0
- package/server/vendored-conf/gitignore/NotepadPP +2 -0
- package/server/vendored-conf/gitignore/OCaml +20 -0
- package/server/vendored-conf/gitignore/Objective-C +43 -0
- package/server/vendored-conf/gitignore/Opa +13 -0
- package/server/vendored-conf/gitignore/OpenCart +11 -0
- package/server/vendored-conf/gitignore/OracleForms +8 -0
- package/server/vendored-conf/gitignore/Packer +5 -0
- package/server/vendored-conf/gitignore/Perl +20 -0
- package/server/vendored-conf/gitignore/Phalcon +2 -0
- package/server/vendored-conf/gitignore/PhpStorm +63 -0
- package/server/vendored-conf/gitignore/PlayFramework +17 -0
- package/server/vendored-conf/gitignore/Plone +18 -0
- package/server/vendored-conf/gitignore/Prestashop +25 -0
- package/server/vendored-conf/gitignore/Processing +7 -0
- package/server/vendored-conf/gitignore/Python +58 -0
- package/server/vendored-conf/gitignore/Qooxdoo +5 -0
- package/server/vendored-conf/gitignore/Qt +34 -0
- package/server/vendored-conf/gitignore/R +16 -0
- package/server/vendored-conf/gitignore/ROS +47 -0
- package/server/vendored-conf/gitignore/Rails +35 -0
- package/server/vendored-conf/gitignore/Redcar +1 -0
- package/server/vendored-conf/gitignore/Redis +3 -0
- package/server/vendored-conf/gitignore/RhodesRhomobile +9 -0
- package/server/vendored-conf/gitignore/Ruby +36 -0
- package/server/vendored-conf/gitignore/Rust +11 -0
- package/server/vendored-conf/gitignore/SBT +9 -0
- package/server/vendored-conf/gitignore/SCons +2 -0
- package/server/vendored-conf/gitignore/SVN +1 -0
- package/server/vendored-conf/gitignore/Sass +2 -0
- package/server/vendored-conf/gitignore/Scala +17 -0
- package/server/vendored-conf/gitignore/Scrivener +7 -0
- package/server/vendored-conf/gitignore/Sdcc +8 -0
- package/server/vendored-conf/gitignore/SeamGen +26 -0
- package/server/vendored-conf/gitignore/SketchUp +1 -0
- package/server/vendored-conf/gitignore/SlickEdit +11 -0
- package/server/vendored-conf/gitignore/Stella +12 -0
- package/server/vendored-conf/gitignore/SublimeText +14 -0
- package/server/vendored-conf/gitignore/SugarCRM +25 -0
- package/server/vendored-conf/gitignore/Swift +43 -0
- package/server/vendored-conf/gitignore/Symfony +37 -0
- package/server/vendored-conf/gitignore/SymphonyCMS +6 -0
- package/server/vendored-conf/gitignore/SynopsysVCS +36 -0
- package/server/vendored-conf/gitignore/Tags +14 -0
- package/server/vendored-conf/gitignore/TeX +136 -0
- package/server/vendored-conf/gitignore/TextMate +3 -0
- package/server/vendored-conf/gitignore/Textpattern +11 -0
- package/server/vendored-conf/gitignore/TortoiseGit +2 -0
- package/server/vendored-conf/gitignore/TurboGears2 +20 -0
- package/server/vendored-conf/gitignore/Typo3 +20 -0
- package/server/vendored-conf/gitignore/Umbraco +17 -0
- package/server/vendored-conf/gitignore/Unity +21 -0
- package/server/vendored-conf/gitignore/UnrealEngine +76 -0
- package/server/vendored-conf/gitignore/VVVV +6 -0
- package/server/vendored-conf/gitignore/Vagrant +1 -0
- package/server/vendored-conf/gitignore/Vim +6 -0
- package/server/vendored-conf/gitignore/VirtualEnv +10 -0
- package/server/vendored-conf/gitignore/VisualStudio +217 -0
- package/server/vendored-conf/gitignore/VisualStudioCode +2 -0
- package/server/vendored-conf/gitignore/Waf +3 -0
- package/server/vendored-conf/gitignore/WebMethods +14 -0
- package/server/vendored-conf/gitignore/WebStorm +63 -0
- package/server/vendored-conf/gitignore/Windows +18 -0
- package/server/vendored-conf/gitignore/WordPress +18 -0
- package/server/vendored-conf/gitignore/Xcode +23 -0
- package/server/vendored-conf/gitignore/XilinxISE +57 -0
- package/server/vendored-conf/gitignore/Xojo +11 -0
- package/server/vendored-conf/gitignore/Yeoman +6 -0
- package/server/vendored-conf/gitignore/Yii +6 -0
- package/server/vendored-conf/gitignore/ZendFramework +20 -0
- package/server/vendored-conf/gitignore/Zephir +26 -0
- package/server/vendored-conf/gitignore/macOS +25 -0
- package/server/vendored-conf/label/Default +7 -0
- package/server/vendored-conf/license/Abstyles License +10 -0
- package/server/vendored-conf/license/Academic Free License v1.1 +22 -0
- package/server/vendored-conf/license/Academic Free License v1.2 +27 -0
- package/server/vendored-conf/license/Academic Free License v2.0 +43 -0
- package/server/vendored-conf/license/Academic Free License v2.1 +44 -0
- package/server/vendored-conf/license/Academic Free License v3.0 +42 -0
- package/server/vendored-conf/license/Affero General Public License v1.0 +84 -0
- package/server/vendored-conf/license/Apache License 1.0 +19 -0
- package/server/vendored-conf/license/Apache License 1.1 +20 -0
- package/server/vendored-conf/license/Apache License 2.0 +72 -0
- package/server/vendored-conf/license/Artistic License 1.0 +48 -0
- package/server/vendored-conf/license/Artistic License 2.0 +84 -0
- package/server/vendored-conf/license/BSD 2-clause License +8 -0
- package/server/vendored-conf/license/BSD 3-clause License +10 -0
- package/server/vendored-conf/license/BSD 4-clause License +13 -0
- package/server/vendored-conf/license/Creative Commons CC0 1.0 Universal +39 -0
- package/server/vendored-conf/license/Eclipse Public License 1.0 +72 -0
- package/server/vendored-conf/license/Educational Community License v1.0 +22 -0
- package/server/vendored-conf/license/Educational Community License v2.0 +97 -0
- package/server/vendored-conf/license/GNU Affero General Public License v3.0 +241 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.1 +118 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.2 +129 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.3 +148 -0
- package/server/vendored-conf/license/GNU General Public License v1.0 +99 -0
- package/server/vendored-conf/license/GNU General Public License v2.0 +116 -0
- package/server/vendored-conf/license/GNU General Public License v3.0 +231 -0
- package/server/vendored-conf/license/GNU Lesser General Public License v2.1 +174 -0
- package/server/vendored-conf/license/GNU Lesser General Public License v3.0 +70 -0
- package/server/vendored-conf/license/GNU Library General Public License v2.0 +173 -0
- package/server/vendored-conf/license/ISC license +6 -0
- package/server/vendored-conf/license/MIT License +8 -0
- package/server/vendored-conf/license/Mozilla Public License 1.0 +122 -0
- package/server/vendored-conf/license/Mozilla Public License 1.1 +142 -0
- package/server/vendored-conf/license/Mozilla Public License 2.0 +143 -0
- package/server/vendored-conf/readme/Default +3 -0
- package/src/dsh-host.js +168 -0
- package/src/index.js +682 -0
- package/src/runner.js +364 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// git hook subcommand: pre-receive/update/post-receive, mirroring cmd/gogs/hook.go.
|
|
2
|
+
// Environment carries the auth/repo context (ComposeHookEnvs).
|
|
3
|
+
import * as db from './db/db.js';
|
|
4
|
+
export async function runHook(name) {
|
|
5
|
+
if (name === 'pre-receive' || name === 'update') {
|
|
6
|
+
// branch protection enforcement goes here; accept by default
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
if (name !== 'post-receive') {
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
console.error('[hook:dbg] name=%s owner=%s repo=%s user=%s', name, process.env.GOGS_REPO_OWNER_NAME, process.env.GOGS_REPO_NAME, process.env.GOGS_AUTH_USER_ID);
|
|
13
|
+
const ownerName = process.env.GOGS_REPO_OWNER_NAME ?? '';
|
|
14
|
+
const repoName = process.env.GOGS_REPO_NAME ?? '';
|
|
15
|
+
const userID = Number(process.env.GOGS_AUTH_USER_ID ?? 0);
|
|
16
|
+
const owner = db.getUserByUsername(ownerName);
|
|
17
|
+
const repo = owner ? db.getRepoByOwnerAndName(owner, repoName) : null;
|
|
18
|
+
const doer = db.getUserByID(userID);
|
|
19
|
+
if (!repo || !doer) {
|
|
20
|
+
console.error('[hook] missing context: owner=%s repo=%s user=%d repoObj=%s', ownerName, repoName, userID, !!repo);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
console.error('[hook:dbg] context ok, reading stdin');
|
|
24
|
+
// stdin lines: "<old> <new> <ref>"
|
|
25
|
+
const input = await new Promise((resolve) => {
|
|
26
|
+
let data = '';
|
|
27
|
+
process.stdin.setEncoding('utf8');
|
|
28
|
+
process.stdin.on('data', (d) => (data += d));
|
|
29
|
+
process.stdin.on('end', () => resolve(data));
|
|
30
|
+
});
|
|
31
|
+
const { processPushUpdate } = await import('./gitx/service.js');
|
|
32
|
+
const { pendingDeliveries } = await import('./webhook.js');
|
|
33
|
+
for (const line of input.split('\n')) {
|
|
34
|
+
const parts = line.trim().split(/\s+/);
|
|
35
|
+
if (parts.length < 3)
|
|
36
|
+
continue;
|
|
37
|
+
const [oldSha, newSha, ref] = parts;
|
|
38
|
+
console.error('[hook:dbg] processing line: %s %s %s', oldSha, newSha, ref);
|
|
39
|
+
// wiki pushes are handled by their own flows
|
|
40
|
+
if (repo.RepoPath().includes('.wiki.git'))
|
|
41
|
+
continue;
|
|
42
|
+
try {
|
|
43
|
+
await processPushUpdate({ doer, repo, refName: ref, oldSha, newSha, pusherID: doer.id });
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
console.error(`[hook] push update failed: ${e?.stack ?? e?.message ?? e}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// let async webhook deliveries flush before the hook process exits
|
|
50
|
+
await Promise.allSettled(Array.from(pendingDeliveries));
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ts-gogs entry point.
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as url from 'node:url';
|
|
6
|
+
import { conf } from './conf.js';
|
|
7
|
+
import { startServer } from './server.js';
|
|
8
|
+
import { db } from './db/db.js';
|
|
9
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
10
|
+
async function main() {
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
// work directory = repo root (contains templates/, public/, vendored-conf/)
|
|
13
|
+
const workDir = path.resolve(__dirname, '..');
|
|
14
|
+
const customDir = process.env.GOGS_CUSTOM ?? path.join(workDir, 'custom');
|
|
15
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
16
|
+
// --config=<path> (gogs CLI convention, used by authorized_keys serv entries)
|
|
17
|
+
const configFlag = argv.find((a) => a.startsWith('--config='));
|
|
18
|
+
const confOverride = configFlag ? configFlag.slice('--config='.length).replace(/^'|'$/g, '') : process.env.GOGS_CUSTOM_CONF;
|
|
19
|
+
conf.load(workDir, customDir, confOverride);
|
|
20
|
+
// asset cache-busting version: real commit when serving from a git checkout
|
|
21
|
+
try {
|
|
22
|
+
conf.buildCommit = execSync('git rev-parse --short HEAD', { cwd: workDir }).toString().trim();
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
// git delegate hook entrypoints (config must be loaded before dispatch)
|
|
26
|
+
if (argv[0] === 'hook') {
|
|
27
|
+
const { runHook } = await import('./hook.js');
|
|
28
|
+
await runHook(argv[1] ?? '');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(`${conf.brandName} ${conf.version} (ts-gogs)`);
|
|
32
|
+
console.log(`Work directory: ${workDir}`);
|
|
33
|
+
console.log(`Custom path: ${customDir}`);
|
|
34
|
+
// git version check (gogs requires >= 1.8.3)
|
|
35
|
+
const { gitVersion } = await import('./gitx/git.js');
|
|
36
|
+
try {
|
|
37
|
+
const v = await gitVersion();
|
|
38
|
+
console.log(`Git version: ${v}`);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.error('FATAL: git binary not found');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
// gogs NewRepoContext: ensure a global git identity exists
|
|
45
|
+
const { gitOK } = await import('./gitx/git.js');
|
|
46
|
+
if (!(await gitOK(process.cwd(), 'config', '--global', 'user.email'))) {
|
|
47
|
+
await gitOK(process.cwd(), 'config', '--global', 'user.email', 'gogs@fake.local');
|
|
48
|
+
await gitOK(process.cwd(), 'config', '--global', 'user.name', 'Gogs');
|
|
49
|
+
}
|
|
50
|
+
db();
|
|
51
|
+
console.log(`Database: ${conf.dbPath}`);
|
|
52
|
+
// dsh-git-server 变体:启动时刷新全部仓库的 delegate hooks
|
|
53
|
+
// (安装位置迁移/升级后旧路径会失效,upstream gogs 同样在启动时重写)
|
|
54
|
+
{
|
|
55
|
+
const rows = db().prepare('SELECT r.lower_name AS repo, u.name AS owner FROM repository r JOIN user u ON u.id = r.owner_id').all();
|
|
56
|
+
const { createDelegateHooks } = await import('./gitx/service.js');
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
try {
|
|
59
|
+
createDelegateHooks(path.join(conf.repositoryRoot, row.owner, row.repo + '.git'));
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// dsh-git-server 插件模式:
|
|
65
|
+
// DSH_BOOTSTRAP_ADMIN=user:pass — 用户库为空时播种管理员
|
|
66
|
+
// DSH_ADMIN_PASSWORD — 每次启动把该管理员密码重置为此值(设置页轮换密码的落点)
|
|
67
|
+
const bootstrap = process.env.DSH_BOOTSTRAP_ADMIN || '';
|
|
68
|
+
const adminPass = process.env.DSH_ADMIN_PASSWORD || '';
|
|
69
|
+
const adminName = bootstrap.split(':')[0] || 'root';
|
|
70
|
+
if (bootstrap.includes(':') || adminPass) {
|
|
71
|
+
const count = db().prepare('SELECT COUNT(*) AS c FROM user WHERE type = 0').get().c;
|
|
72
|
+
const { randomSalt, encodePassword } = await import('./authx/password.js');
|
|
73
|
+
const now = Math.floor(Date.now() / 1000);
|
|
74
|
+
if (count === 0) {
|
|
75
|
+
const pass = bootstrap.includes(':') ? bootstrap.slice(bootstrap.indexOf(':') + 1) : (adminPass || 'gogs-admin');
|
|
76
|
+
const salt = randomSalt();
|
|
77
|
+
db().prepare('INSERT INTO user (name, lower_name, email, passwd, salt, type, is_admin, created_unix, updated_unix) VALUES (?, ?, ?, ?, ?, 0, 1, ?, ?)')
|
|
78
|
+
.run(adminName, adminName.toLowerCase(), `${adminName}@dsh.local`, encodePassword(pass, salt), salt, now, now);
|
|
79
|
+
console.log(`Bootstrap admin created: ${adminName}`);
|
|
80
|
+
}
|
|
81
|
+
else if (adminPass) {
|
|
82
|
+
// 管理员已存在:重置密码为插件配置值(设置页轮换的真实落点)
|
|
83
|
+
const admin = db().prepare('SELECT id, salt FROM user WHERE type = 0 AND is_admin = 1 ORDER BY id LIMIT 1').get();
|
|
84
|
+
if (admin) {
|
|
85
|
+
const { encodePassword: enc } = await import('./authx/password.js');
|
|
86
|
+
db().prepare('UPDATE user SET passwd = ?, updated_unix = ? WHERE id = ?').run(enc(adminPass, admin.salt), now, admin.id);
|
|
87
|
+
console.log(`Admin password synced from plugin settings`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const server = await startServer();
|
|
92
|
+
// periodic mirror synchronization (gogs InitSyncMirrors)
|
|
93
|
+
const { startMirrorLoop } = await import('./mirror.js');
|
|
94
|
+
startMirrorLoop();
|
|
95
|
+
// dsh-git-server 变体:不启动内置 SSH 服务(git 统一走 HTTP)
|
|
96
|
+
// dsh-git-server 变体减法:不写系统 ~/.ssh/authorized_keys(插件默认
|
|
97
|
+
// DISABLE_SSH,git 统一走 HTTP),避免与宿主机器的 ssh 配置互相干扰。
|
|
98
|
+
console.log(`Available on ${conf.externalURL}`);
|
|
99
|
+
console.log(`Listening ${conf.httpAddr}:${conf.httpPort}`);
|
|
100
|
+
const shutdown = () => {
|
|
101
|
+
console.log('\nShutting down...');
|
|
102
|
+
server.close(() => process.exit(0));
|
|
103
|
+
setTimeout(() => process.exit(0), 3000);
|
|
104
|
+
};
|
|
105
|
+
process.on('SIGINT', shutdown);
|
|
106
|
+
process.on('SIGTERM', shutdown);
|
|
107
|
+
}
|
|
108
|
+
main().catch((e) => {
|
|
109
|
+
console.error('FATAL:', e);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import { conf } from './conf.js';
|
|
5
|
+
import * as db from './db/db.js';
|
|
6
|
+
import { verifyPassword } from './authx/password.js';
|
|
7
|
+
const LFS_CONTENT_TYPE = 'application/vnd.git-lfs+json';
|
|
8
|
+
function hmacSign(data) {
|
|
9
|
+
return crypto.createHmac('sha256', conf.secretKey).update(data).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
export function mintLFSToken(userID, repoID, ttlSeconds = 86400) {
|
|
12
|
+
const expiry = Math.floor(Date.now() / 1000) + ttlSeconds;
|
|
13
|
+
const payload = `${userID}.${repoID}.${expiry}`;
|
|
14
|
+
return { token: `${payload}.${hmacSign(payload)}`, expiresAt: expiry };
|
|
15
|
+
}
|
|
16
|
+
export function verifyLFSToken(token) {
|
|
17
|
+
const idx = token.lastIndexOf('.');
|
|
18
|
+
if (idx < 0)
|
|
19
|
+
return null;
|
|
20
|
+
const payload = token.slice(0, idx);
|
|
21
|
+
const sig = token.slice(idx + 1);
|
|
22
|
+
const expected = hmacSign(payload);
|
|
23
|
+
const a = Buffer.from(sig, 'hex');
|
|
24
|
+
const b = Buffer.from(expected, 'hex');
|
|
25
|
+
if (a.length !== b.length || !crypto.timingSafeEqual(a, b))
|
|
26
|
+
return null;
|
|
27
|
+
const [userID, repoID, expiry] = payload.split('.');
|
|
28
|
+
const p = { userID: Number(userID), repoID: Number(repoID), expiry: Number(expiry) };
|
|
29
|
+
if (!Number.isFinite(p.expiry) || p.expiry < Date.now() / 1000)
|
|
30
|
+
return null;
|
|
31
|
+
return p;
|
|
32
|
+
}
|
|
33
|
+
function validOID(oid) {
|
|
34
|
+
return /^[a-f0-9]{64}$/.test(oid);
|
|
35
|
+
}
|
|
36
|
+
function sendJSON(res, status, body) {
|
|
37
|
+
res.statusCode = status;
|
|
38
|
+
res.setHeader('Content-Type', LFS_CONTENT_TYPE);
|
|
39
|
+
res.end(JSON.stringify(body ?? {}));
|
|
40
|
+
}
|
|
41
|
+
function askCredentials(res) {
|
|
42
|
+
res.setHeader('Lfs-Authenticate', 'Basic realm="Git LFS"');
|
|
43
|
+
sendJSON(res, 401, { message: 'Credentials needed' });
|
|
44
|
+
}
|
|
45
|
+
function lfsObjectPath(oid) {
|
|
46
|
+
return path.join(conf.appDataPath, 'lfs-objects', oid.slice(0, 2), oid.slice(2, 4), oid);
|
|
47
|
+
}
|
|
48
|
+
function readBody(req) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const chunks = [];
|
|
51
|
+
req.on('data', (d) => chunks.push(d));
|
|
52
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
53
|
+
req.on('error', reject);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** gogs LFS authenticate: RemoteAuth token (from git-lfs-authenticate over SSH),
|
|
57
|
+
* basic user/pass (2FA users rejected), or token as username or password. */
|
|
58
|
+
async function authenticate(req) {
|
|
59
|
+
const header = String(req.headers.authorization ?? '');
|
|
60
|
+
if (header.startsWith('RemoteAuth ')) {
|
|
61
|
+
const payload = verifyLFSToken(header.slice('RemoteAuth '.length).trim());
|
|
62
|
+
if (payload)
|
|
63
|
+
return db.getUserByID(payload.userID);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!header.startsWith('Basic '))
|
|
67
|
+
return null;
|
|
68
|
+
const decoded = Buffer.from(header.slice(6), 'base64').toString('utf8');
|
|
69
|
+
const idx = decoded.indexOf(':');
|
|
70
|
+
const username = idx >= 0 ? decoded.slice(0, idx) : decoded;
|
|
71
|
+
const password = idx >= 0 ? decoded.slice(idx + 1) : '';
|
|
72
|
+
if (!username)
|
|
73
|
+
return null;
|
|
74
|
+
const user = db.getUserByUsername(username);
|
|
75
|
+
if (user && verifyPassword(password, user.salt, user.passwd)) {
|
|
76
|
+
if (is2FAEnabled(user.id))
|
|
77
|
+
return null; // password auth disallowed for 2FA users
|
|
78
|
+
return user;
|
|
79
|
+
}
|
|
80
|
+
// username or password may be an access token
|
|
81
|
+
for (const candidate of [username, password]) {
|
|
82
|
+
const row = db.getAccessTokenBySHA1(candidate);
|
|
83
|
+
if (row) {
|
|
84
|
+
const u = db.getUserByID(row.uid);
|
|
85
|
+
if (u)
|
|
86
|
+
return u;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function is2FAEnabled(userID) {
|
|
92
|
+
return !!db.db().prepare('SELECT 1 FROM two_factor WHERE user_id = ?').get(userID);
|
|
93
|
+
}
|
|
94
|
+
function ensureStorage(oid) {
|
|
95
|
+
const dst = lfsObjectPath(oid);
|
|
96
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
97
|
+
return dst;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Handle an LFS request; returns true when handled.
|
|
101
|
+
* subPath is the part after "/{owner}/{repo}.git/info/lfs".
|
|
102
|
+
*/
|
|
103
|
+
export async function handleLFS(req, res, owner, repo, subPath) {
|
|
104
|
+
const method = (req.method ?? 'GET').toUpperCase();
|
|
105
|
+
const user = await authenticate(req);
|
|
106
|
+
// route: /objects/batch, /objects/basic/:oid, /objects/basic/verify
|
|
107
|
+
const m = /^\/objects\/batch$/.exec(subPath);
|
|
108
|
+
const mBasic = /^\/objects\/basic\/([a-f0-9]{64})$/.exec(subPath);
|
|
109
|
+
const mVerify = /^\/objects\/basic\/verify$/.exec(subPath);
|
|
110
|
+
if (!m && !mBasic && !mVerify) {
|
|
111
|
+
sendJSON(res, 404, { message: 'Not found' });
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (!user) {
|
|
115
|
+
askCredentials(res);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
const mode = db.accessMode(user.id, repo);
|
|
119
|
+
const canRead = mode >= db.AccessMode.READ || (!repo.is_private && !conf.requireSigninView);
|
|
120
|
+
const canWrite = mode >= db.AccessMode.WRITE;
|
|
121
|
+
// ---- POST /objects/batch
|
|
122
|
+
if (m && method === 'POST') {
|
|
123
|
+
if (!canRead) {
|
|
124
|
+
sendJSON(res, 404, { message: 'Not found' });
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (String(req.headers.accept ?? '') !== LFS_CONTENT_TYPE) {
|
|
128
|
+
sendJSON(res, 406, { message: 'Bad `Accept` header' });
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (String(req.headers['content-type'] ?? '') !== LFS_CONTENT_TYPE) {
|
|
132
|
+
sendJSON(res, 400, { message: 'Bad `Content-Type` header' });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
let body;
|
|
136
|
+
try {
|
|
137
|
+
body = JSON.parse((await readBody(req)).toString('utf8'));
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
sendJSON(res, 400, { message: String(e?.message ?? e).replace(/^./, (ch) => ch.toUpperCase()) });
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
const operation = String(body.operation ?? '');
|
|
144
|
+
const baseHref = `${conf.externalURL}${owner.name}/${repo.name}.git/info/lfs/objects/basic`;
|
|
145
|
+
const objects = [];
|
|
146
|
+
if (operation === 'upload') {
|
|
147
|
+
for (const obj of body.objects ?? []) {
|
|
148
|
+
if (validOID(String(obj.oid))) {
|
|
149
|
+
objects.push({
|
|
150
|
+
oid: obj.oid,
|
|
151
|
+
size: obj.size,
|
|
152
|
+
authenticated: true,
|
|
153
|
+
actions: {
|
|
154
|
+
upload: { href: `${baseHref}/${obj.oid}`, header: { 'Content-Type': 'application/octet-stream' } },
|
|
155
|
+
verify: { href: `${baseHref}/verify` },
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
objects.push({ oid: obj.oid, size: obj.size, error: { code: 422, message: 'Object has invalid oid' } });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (operation === 'download') {
|
|
165
|
+
for (const obj of body.objects ?? []) {
|
|
166
|
+
const oid = String(obj.oid);
|
|
167
|
+
const row = db.db().prepare('SELECT * FROM lfs_object WHERE repo_id = ? AND oid = ?').get(repo.id, oid);
|
|
168
|
+
if (!row) {
|
|
169
|
+
objects.push({ oid, size: obj.size, error: { code: 404, message: 'Object does not exist' } });
|
|
170
|
+
}
|
|
171
|
+
else if (row.size !== obj.size) {
|
|
172
|
+
objects.push({ oid, size: obj.size, error: { code: 422, message: 'Object size mismatch' } });
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
objects.push({ oid, size: obj.size, authenticated: true, actions: { download: { href: `${baseHref}/${oid}` } } });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
sendJSON(res, 400, { message: 'Operation not supported' });
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
sendJSON(res, 200, { transfer: 'basic', authentication: 'basic', objects });
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
// ---- GET /objects/basic/:oid (download)
|
|
187
|
+
if (mBasic && method === 'GET') {
|
|
188
|
+
if (!canRead) {
|
|
189
|
+
sendJSON(res, 404, { message: 'Not found' });
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const oid = mBasic[1];
|
|
193
|
+
const row = db.db().prepare('SELECT * FROM lfs_object WHERE repo_id = ? AND oid = ?').get(repo.id, oid);
|
|
194
|
+
if (!row) {
|
|
195
|
+
sendJSON(res, 404, { message: 'Object does not exist' });
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
const file = lfsObjectPath(oid);
|
|
199
|
+
if (!fs.existsSync(file)) {
|
|
200
|
+
sendJSON(res, 404, { message: 'Object does not exist' });
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
res.setHeader('Content-Type', 'application/octet-stream');
|
|
204
|
+
res.setHeader('Content-Length', String(fs.statSync(file).size));
|
|
205
|
+
fs.createReadStream(file).pipe(res);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
// ---- PUT /objects/basic/:oid (upload)
|
|
209
|
+
if (mBasic && method === 'PUT') {
|
|
210
|
+
if (!canWrite) {
|
|
211
|
+
sendJSON(res, 404, { message: 'Not found' });
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (String(req.headers['content-type'] ?? '') !== 'application/octet-stream') {
|
|
215
|
+
sendJSON(res, 400, { message: 'Bad `Content-Type` header' });
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
const oid = mBasic[1];
|
|
219
|
+
const content = await readBody(req);
|
|
220
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
221
|
+
if (hash !== oid) {
|
|
222
|
+
sendJSON(res, 422, { message: 'Content-Length and Oid mismatches' });
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
const dst = ensureStorage(oid);
|
|
226
|
+
fs.writeFileSync(dst, content);
|
|
227
|
+
db.db()
|
|
228
|
+
.prepare('INSERT OR REPLACE INTO lfs_object (repo_id, oid, size, storage, created_at) VALUES (?,?,?,?,?)')
|
|
229
|
+
.run(repo.id, oid, content.length, 'local', new Date().toISOString().replace('T', ' ').replace('Z', ''));
|
|
230
|
+
res.statusCode = 200;
|
|
231
|
+
res.end();
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
// ---- POST /objects/basic/verify
|
|
235
|
+
if (mVerify && method === 'POST') {
|
|
236
|
+
if (!canWrite) {
|
|
237
|
+
sendJSON(res, 404, { message: 'Not found' });
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
let body;
|
|
241
|
+
try {
|
|
242
|
+
body = JSON.parse((await readBody(req)).toString('utf8'));
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
sendJSON(res, 400, { message: String(e?.message ?? e) });
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
const oid = String(body.oid ?? '');
|
|
249
|
+
const size = Number(body.size ?? -1);
|
|
250
|
+
const row = db.db().prepare('SELECT * FROM lfs_object WHERE repo_id = ? AND oid = ?').get(repo.id, oid);
|
|
251
|
+
if (!row || !validOID(oid)) {
|
|
252
|
+
sendJSON(res, 404, { message: 'Object does not exist' });
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (row.size !== size) {
|
|
256
|
+
sendJSON(res, 422, { message: 'Object size mismatch' });
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
sendJSON(res, 200, {});
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
sendJSON(res, 405, { message: 'Method not allowed' });
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=lfsx.js.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Minimal SMTP client (implicit TLS on 465, STARTTLS on 587, plain otherwise)
|
|
2
|
+
// powering gogs [mailer] flows: test mail, account activation, password reset.
|
|
3
|
+
import * as net from 'node:net';
|
|
4
|
+
import * as tls from 'node:tls';
|
|
5
|
+
import { conf } from './conf.js';
|
|
6
|
+
function readReply(socket, expect) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
let buf = '';
|
|
9
|
+
const onData = (d) => {
|
|
10
|
+
buf += d.toString('utf8');
|
|
11
|
+
// last line fully received and matches expected code
|
|
12
|
+
const lines = buf.split(/\r?\n/).filter(Boolean);
|
|
13
|
+
const last = lines[lines.length - 1] ?? '';
|
|
14
|
+
if (/^\d{3} /.test(last)) {
|
|
15
|
+
socket.removeListener('data', onData);
|
|
16
|
+
if (expect.test(last))
|
|
17
|
+
resolve(buf);
|
|
18
|
+
else
|
|
19
|
+
reject(new Error(`SMTP unexpected reply: ${last}`));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
socket.on('data', onData);
|
|
23
|
+
socket.once('error', reject);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function sendLine(socket, line) {
|
|
27
|
+
socket.write(line + '\r\n');
|
|
28
|
+
}
|
|
29
|
+
export async function sendMail(mail) {
|
|
30
|
+
// HOST may carry an explicit port (gogs semantics: `host:port`)
|
|
31
|
+
let host = conf.emailHost.split(':')[0];
|
|
32
|
+
let port = conf.emailPort;
|
|
33
|
+
if (conf.emailHost.includes(':')) {
|
|
34
|
+
const p = Number(conf.emailHost.split(':')[1]);
|
|
35
|
+
if (Number.isFinite(p) && p > 0)
|
|
36
|
+
port = p;
|
|
37
|
+
}
|
|
38
|
+
const from = conf.emailFrom || mail.from;
|
|
39
|
+
const subject = conf.emailSubjectPrefix + mail.subject;
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
let socket;
|
|
42
|
+
if (port === 465) {
|
|
43
|
+
socket = tls.connect({ host, port, rejectUnauthorized: !conf.emailSkipVerify }, () => run());
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
socket = net.connect({ host, port }, () => run());
|
|
47
|
+
}
|
|
48
|
+
socket.setTimeout(15000, () => {
|
|
49
|
+
socket.destroy(new Error('SMTP timeout'));
|
|
50
|
+
});
|
|
51
|
+
socket.on('error', reject);
|
|
52
|
+
let started = false;
|
|
53
|
+
async function run() {
|
|
54
|
+
try {
|
|
55
|
+
if (!started) {
|
|
56
|
+
started = true;
|
|
57
|
+
await readReply(socket, /^220/);
|
|
58
|
+
const ehloHost = conf.domain || 'localhost';
|
|
59
|
+
sendLine(socket, `EHLO ${ehloHost}`);
|
|
60
|
+
await readReply(socket, /^250/);
|
|
61
|
+
if (conf.emailUseTLS && port !== 465) {
|
|
62
|
+
sendLine(socket, 'STARTTLS');
|
|
63
|
+
await readReply(socket, /^220/);
|
|
64
|
+
const secure = tls.connect({ socket, host, port, rejectUnauthorized: !conf.emailSkipVerify }, async () => {
|
|
65
|
+
socket.removeAllListeners('data');
|
|
66
|
+
try {
|
|
67
|
+
await smtpSession(socket);
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
reject(e);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
secure.on('error', reject);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await smtpSession(socket);
|
|
78
|
+
resolve();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
reject(e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function smtpSession(sock) {
|
|
86
|
+
if (conf.emailUser) {
|
|
87
|
+
sendLine(sock, 'AUTH LOGIN');
|
|
88
|
+
await readReply(sock, /^334/);
|
|
89
|
+
sendLine(sock, Buffer.from(conf.emailUser).toString('base64'));
|
|
90
|
+
await readReply(sock, /^334/);
|
|
91
|
+
sendLine(sock, Buffer.from(conf.emailPasswd).toString('base64'));
|
|
92
|
+
await readReply(sock, /^235/);
|
|
93
|
+
}
|
|
94
|
+
sendLine(sock, `MAIL FROM:<${from.replace(/.*</, '').replace(/>.*/, '')}>`);
|
|
95
|
+
await readReply(sock, /^250/);
|
|
96
|
+
sendLine(sock, `RCPT TO:<${mail.to}>`);
|
|
97
|
+
await readReply(sock, /^250/);
|
|
98
|
+
sendLine(sock, 'DATA');
|
|
99
|
+
await readReply(sock, /^354/);
|
|
100
|
+
const headers = `From: ${from}\r\nTo: ${mail.to}\r\nSubject: ${subject}\r\nMIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n`;
|
|
101
|
+
sendLine(sock, headers + mail.body.replace(/\r?\n\./g, '\r\n..') + '\r\n.');
|
|
102
|
+
await readReply(sock, /^250/);
|
|
103
|
+
sendLine(sock, 'QUIT');
|
|
104
|
+
sock.end();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Activation mail body — mirrors gogs mail/auth/activate.tmpl essentials. */
|
|
109
|
+
export function sendActivateMail(user, code) {
|
|
110
|
+
const link = `${conf.externalURL}user/activate?code=${encodeURIComponent(code)}`;
|
|
111
|
+
return sendMail({
|
|
112
|
+
from: conf.emailFrom,
|
|
113
|
+
to: user.email,
|
|
114
|
+
subject: 'Please activate your account',
|
|
115
|
+
body: `<p>Hi <b>${user.name}</b>, thanks for registering! Please click the following link to activate your account within <b>${conf.activateCodeLives} minutes</b>.</p><p><a href="${link}">${link}</a></p><p>Not working? Paste this code: <code>${code}</code></p>`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export function sendResetPasswordMail(user, code) {
|
|
119
|
+
const link = `${conf.externalURL}user/reset_password?code=${encodeURIComponent(code)}`;
|
|
120
|
+
return sendMail({
|
|
121
|
+
from: conf.emailFrom,
|
|
122
|
+
to: user.email,
|
|
123
|
+
subject: 'Reset your password',
|
|
124
|
+
body: `<p>Hi <b>${user.name}</b>, someone requested a password reset. Click within <b>${conf.resetPwdCodeLives} minutes</b> to continue.</p><p><a href="${link}">${link}</a></p>`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=mailer.js.map
|