パスワード管理パターン
:LiTarget: 用途
チームで共有するアカウント情報・APIキーの安全な管理パターン。
:LiSparkle: 特徴
- 暗号化保管
- チーム共有
- カテゴリ分類
- アクセスログ
:LiCode: 実コード(SCALE Base より自動抽出)
:LiInfo:
lib/passwords-data.tsの中身そのもの。コピペ即可。
// パスワード管理データ層(D1 + Cloudflare Pages Functions経由)
// 全件取得時にサーバー側で復号して返すため、UI側は通常の文字列として扱える。
// 「みんなで共有」する設計:登録するとscale-base全体に反映される。
export const PW_CATEGORIES = [
'業務アプリ',
'インフラ・サーバー',
'マーケティング',
'金融・決済',
'SNS',
'クライアント連携',
'その他',
];
export interface PasswordEntry {
id: string;
service: string;
url: string;
username: string;
password: string;
category: string;
owner: string;
memo: string;
tags: string[];
addedBy?: string;
addedVia?: 'manual' | 'slack-bot';
createdAt: string;
lastUpdated: string;
}
export async function fetchPasswords(): Promise<PasswordEntry[]> {
try {
const res = await fetch('/api/passwords', { cache: 'no-store' });
if (!res.ok) return [];
const data = await res.json();
return (data.passwords || []) as PasswordEntry[];
} catch {
return [];
}
}
export async function createPassword(entry: {
service: string; url?: string; username?: string; password: string;
category?: string; owner?: string; memo?: string; tags?: string[]; addedBy?: string;
}): Promise<{ ok: boolean; id?: string; error?: string }> {
try {
const res = await fetch('/api/passwords', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entry),
});
const data = await res.json();
if (!res.ok) return { ok: false, error: data.error || `HTTP ${res.status}` };
return { ok: true, id: data.id };
} catch (err) {
return { ok: false, error: String(err) };
}
}
export async function updatePassword(id: string, patch: {
service?: string; url?: string; username?: string; password?: string;
category?: string; owner?: string; memo?: string; tags?: string[];
}): Promise<boolean> {
try {
const res = await fetch(`/api/passwords/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch),
});
return res.ok;
} catch {
return false;
}
}
export async function deletePassword(id: string): Promise<boolean> {
try {
const res = await fetch(`/api/passwords/${id}`, { method: 'DELETE' });
return res.ok;
} catch {
return false;
}
}
export function genId(): string {
return 'pw-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6);
}
export function nowISO(): string {
return new Date().toISOString();
}
export function copyToClipboard(text: string): Promise<boolean> {
if (typeof navigator === 'undefined' || !navigator.clipboard) return Promise.resolve(false);
return navigator.clipboard.writeText(text).then(() => true).catch(() => false);
}
// ─── 旧API(localStorageベース)後方互換用。次の改修で削除予定 ───
const LEGACY_KEY = 'scale-passwords';
export function loadPasswords(): PasswordEntry[] {
if (typeof window === 'undefined') return [];
try {
const v = localStorage.getItem(LEGACY_KEY);
return v ? JSON.parse(v) : [];
} catch { return []; }
}
export function savePasswords(list: PasswordEntry[]) {
if (typeof window === 'undefined') return;
localStorage.setItem(LEGACY_KEY, JSON.stringify(list));
}
:LiFolder: ソースファイルのパス
/Users/oogushiyuuki/Library/CloudStorage/GoogleDrive-y-ogushi@scale-group.co.jp/マイドライブ/AI/scale-base/lib/passwords-data.ts
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加