diff --git a/src/tools/show-signin-history.ts b/src/tools/show-signin-history.ts
new file mode 100644
index 000000000..e77071032
--- /dev/null
+++ b/src/tools/show-signin-history.ts
@@ -0,0 +1,57 @@
+// node built/tools/show-signin-history username
+//  => {Success} {Date} {IPAddrsss}
+
+// node built/tools/show-signin-history username user-agent,x-forwarded-for
+//  with user-agent and x-forwarded-for
+
+// node built/tools/show-signin-history username all
+//  with full request headers
+
+import User from '../models/user';
+import Signin from '../models/signin';
+
+async function main(username: string, headers: string[]) {
+	const user = await User.findOne({
+		host: null,
+		usernameLower: username.toLowerCase(),
+	});
+
+	if (user === null) throw 'User not found';
+
+	const history = await Signin.find({
+		userId: user._id
+	});
+
+	for (const signin of history) {
+		console.log(`${signin.success ? 'OK' : 'NG'} ${signin.createdAt ? signin.createdAt.toISOString() : 'Unknown'} ${signin.ip}`);
+
+		// headers
+		if (headers != null) {
+			for (const key of Object.keys(signin.headers)) {
+				if (headers.includes('all') || headers.includes(key)) {
+					console.log(`   ${key}: ${signin.headers[key]}`);
+				}
+			}
+		}
+	}
+}
+
+// get args
+const args = process.argv.slice(2);
+
+let username = args[0];
+let headers: string[];
+
+if (args[1] != null) {
+	headers = args[1].split(/,/).map(header => header.toLowerCase());
+}
+
+// normalize args
+username = username.replace(/^@/, '');
+
+main(username, headers).then(() => {
+	process.exit(0);
+}).catch(e => {
+	console.warn(e);
+	process.exit(1);
+});