auth-mn 0.2.0 → 1.0.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 CHANGED
@@ -1 +1,187 @@
1
- # AUTH.MN
1
+ # auth-mn
2
+
3
+ **Auth.mn** — таны апп-уудын нэгдсэн хэрэглэгчийн систем. Хэрэглэгч бүртгүүлэх, нэвтрэх
4
+ (утас / имэйл OTP, Google, Facebook, Steam, QR), профайл, төхөөрөмж, мэдэгдлийг Auth.mn
5
+ хариуцна; таны апп зөвхөн **хэрэглэгчийн token**-ийг хүлээж аваад Auth.mn-ээс мэдээллийг нь
6
+ асууна.
7
+
8
+ Энэ сан нь таны **backend (Node.js)** дээр ажиллана:
9
+
10
+ | Та юу хийх вэ | Сангийн функц | Шаардлагатай эрх |
11
+ |---|---|---|
12
+ | Frontend-ээс ирсэн хэрэглэгчийн token шалгаж мэдээллийг авах | `user.verify(token)` / `user.middleware()` | `user.verify` |
13
+ | user_id / утас / имэйлээр хэрэглэгч хайх | `user.find({...})` | `user.read` |
14
+ | Push мэдэгдэл, SMS, имэйл илгээх | `notification.push / sms / mail` | `notification.*` |
15
+ | Хэрэглэгчийн нээлттэй апп дээр realtime toast / төлөв харуулах | `socket.toast / status / orderStatus / emit` | `socket.*` |
16
+
17
+ Frontend дээр нэвтрэх товч, QR нэвтрэлт хэрэгтэй бол **[auth-mn-react](https://www.npmjs.com/package/auth-mn-react)** санг хэрэглэнэ.
18
+
19
+ ```bash
20
+ npm i auth-mn
21
+ ```
22
+
23
+ ---
24
+
25
+ ## 1. Client бүртгүүлэх
26
+
27
+ Auth.mn **admin panel → Апп-ууд (Client)** дээр апп-аа бүртгэхэд `client_id` (`cl_…`) болон
28
+ `client_secret` (`sk_…`) олгоно. Secret **нэг л удаа** харагдана — `.env`-дээ хадгална.
29
+ Тухайн апп ямар API дуудаж болохыг (эрхүүдийг) admin тэндээ сонгоно; эрхгүй API дуудвал
30
+ `403` + ямар эрх дутууг хэлсэн message ирнэ.
31
+
32
+ ```env
33
+ AUTH_MN_CLIENT_ID=cl_xxxxxxxxxxxxxxxx
34
+ AUTH_MN_CLIENT_SECRET=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
35
+ ```
36
+
37
+ ## 2. Тохиргоо
38
+
39
+ ```ts
40
+ import authMn, { setAuth, setHost } from "auth-mn";
41
+
42
+ setHost("prod"); // "staging" эсвэл setHost({ MAIN: "https://api.auth.mn" })
43
+ setAuth({
44
+ client_id: process.env.AUTH_MN_CLIENT_ID!,
45
+ client_secret: process.env.AUTH_MN_CLIENT_SECRET!
46
+ });
47
+ ```
48
+
49
+ Үүний дараа юу ч хийх шаардлагагүй: сан `client_id + secret`-ээр access token (1 цаг) болон
50
+ refresh token (30 хоног) авч, хугацаа дуусахаас өмнө өөрөө сунгана. 401 ирвэл token-оо
51
+ сэргээж нэг удаа дахин оролдоно. **Бүх функц `{ success, message, data, status }` буцна, throw хийхгүй.**
52
+
53
+ ## 3. Хэрэглэгч нэвтрүүлэх (гол урсгал)
54
+
55
+ ```
56
+ [Frontend] auth-mn-react → Auth.mn дээр нэвтэрнэ → таны сайт руу ?authToken=... буцна
57
+ (эсвэл QR / OTP-оор token авна)
58
+ │ Authorization: Bearer <authToken>
59
+ ▼
60
+ [Таны backend] authMn.user.verify(authToken) ──► Auth.mn (client token + user.verify эрх)
61
+ │ token-ийг задалж, хэрэглэгчийн
62
+ ▼ мэдээллийг буцаана
63
+ өөрийн session / JWT үүсгэнэ, хэрэглэгчийг DB-дээ user_id-аар холбоно
64
+ ```
65
+
66
+ ### 3.1 Нэвтрэх endpoint
67
+
68
+ ```ts
69
+ app.post("/api/auth/callback", async (req, res) => {
70
+ const v = await authMn.user.verify(req.body.authToken);
71
+ if (!v.success) return res.status(401).json({ message: v.message });
72
+
73
+ const u = v.data.user; // user_id, user_name, primary_phone, primary_email, profile_image_url, ...
74
+ // 1) өөрийн DB-д user_id-аар хайж байхгүй бол үүсгэнэ
75
+ // 2) өөрийн session / JWT үүсгэж cookie-д хадгална
76
+ res.json({ success: true, user: u });
77
+ });
78
+ ```
79
+
80
+ `v.data`:
81
+
82
+ ```ts
83
+ {
84
+ user: UserT; // доорх бүтэц
85
+ exp: number | null; // хэрэглэгчийн token дуусах хугацаа (unix sec)
86
+ client: { client_id, name } // ямар client-ээр шалгасан
87
+ }
88
+ ```
89
+
90
+ Шалгалт бүр хэрэглэгчийн **нэвтрэлтийн түүхэнд** "`<таны домэйн>`-д дамжуулсан" гэж бүртгэгдэнэ —
91
+ хэрэглэгч Auth.mn дээрээ аль апп-д хэзээ нэвтэрснээ харна.
92
+
93
+ ### 3.2 Route хамгаалах middleware
94
+
95
+ Хэрэглэгчийн token-ийг хүсэлт бүрт шалгах бол:
96
+
97
+ ```ts
98
+ // Authorization: Bearer <token> (эсвэл ?authToken= / body.authToken)
99
+ app.get("/api/me", authMn.user.middleware(), (req, res) => {
100
+ res.json(req.authMnUser); // UserT
101
+ });
102
+
103
+ app.get("/api/feed", authMn.user.middleware({ required: false }), (req, res) => {
104
+ // нэвтрээгүй бол req.authMnUser === null, алдаа өгөхгүй
105
+ });
106
+ ```
107
+
108
+ > Хүсэлт бүрт Auth.mn руу нэг дуудлага хийдэг тул ачаалал ихтэй бол 3.1-ийн адил нэвтрэх үед нэг удаа
109
+ > шалгаад өөрийн session/JWT ашиглахыг зөвлөнө.
110
+
111
+ ### 3.3 Хэрэглэгч хайх
112
+
113
+ ```ts
114
+ const r = await authMn.user.find({ user_id: 5742248 }); // эсвэл { phone_number: "99112233" } / { email }
115
+ if (r.success) console.log(r.data.user_name);
116
+ ```
117
+
118
+ ### UserT
119
+
120
+ ```ts
121
+ {
122
+ user_id: number; // Auth.mn-ийн тогтмол ID — өөрийн DB-д үүгээр холбоно
123
+ user_name: string;
124
+ nick_name: string;
125
+ primary_phone: string | null;
126
+ primary_email: string | null;
127
+ emails: { email, verified, primary }[];
128
+ phone_numbers: { number, verified, primary }[];
129
+ profile_image_url: string;
130
+ gender?: "MEN" | "WOMEN";
131
+ date_of_birth: Date | null;
132
+ auth_profiles: { google_id, facebook_id, steam_id, ... };
133
+ sign_up_date, last_login, ...
134
+ }
135
+ ```
136
+
137
+ ## 4. Мэдэгдэл
138
+
139
+ ```ts
140
+ await authMn.notification.push({
141
+ user_id: 5742248,
142
+ target_app: "MY-APP",
143
+ body: { title: "Захиалга", body: "Захиалга #123 батлагдлаа", button: { title: "Харах", url: "https://..." } }
144
+ }); // notification.push
145
+ await authMn.notification.sms({ to: "99112233", from: "MYAPP", text: "Код: 1234" }); // notification.sms
146
+ await authMn.notification.mail({
147
+ type: "notification", to: ["a@b.mn"], subject: "Сайн уу",
148
+ body: { name: "Бат", title: "Тавтай морил", desc: "..." }
149
+ }); // notification.mail
150
+ ```
151
+
152
+ ## 5. Socket (realtime)
153
+
154
+ Хэрэглэгч Auth.mn-тэй холбогдсон аль нэг апп-аа нээлттэй байхад:
155
+
156
+ ```ts
157
+ await authMn.socket.toast({ user_id, type: "info", text: "Төлбөр амжилттай" }); // socket.toast
158
+ await authMn.socket.status({ user_id, id: "order-123", type: "Loading", title: "Боловсруулж байна" }); // socket.status
159
+ await authMn.socket.status({ user_id, id: "order-123", type: "Success", title: "Амжилттай", amount: 15000 });
160
+ await authMn.socket.orderStatus({ user_id, order_id: "order-123" }); // socket.status
161
+ await authMn.socket.emit([{ user_id, emit_name: "my-event", data: { any: 1 } }]); // socket.emit
162
+ ```
163
+
164
+ ## 6. Client өөрийн мэдээлэл
165
+
166
+ ```ts
167
+ await authMn.auth.me(); // { client_id, name, permissions, ... }
168
+ authMn.auth.hasPermission("notification.sms"); // сүүлийн token-оос
169
+ await authMn.auth.permissions(); // Auth.mn-ийн бүх боломжит эрх
170
+ await authMn.auth.revoke(); // refresh token-оо хүчингүй болгоод cache цэвэрлэнэ
171
+ ```
172
+
173
+ ## Алдаа
174
+
175
+ | status | Шалтгаан |
176
+ |---|---|
177
+ | 401 | client_id/secret буруу, client идэвхгүй, эсвэл хэрэглэгчийн token хүчингүй |
178
+ | 403 | Энэ API-д шаардлагатай эрх client-д олгогдоогүй — admin panel дээр нэмнэ |
179
+ | 0 | Сүлжээний алдаа (`message`-д шалтгаан) |
180
+
181
+ ## 0.x → 1.0 шилжилт
182
+
183
+ - `config.auth.username / password`, `auth.TOKEN({username,password})` → `setAuth({ client_id, client_secret })`.
184
+ Хуучин client бүртгэлүүд admin panel дээр **Secret шинэчлэх** дарж secret авна.
185
+ - `find.USER / MERCHANT / ADMIN / CLIENT / CRM` **хасагдсан** — хэрэглэгчийн мэдээлэл `user.verify` / `user.find`-ээр.
186
+ - `notification.SinglePushNoti / SMS / MAIL`, `socket.ShowToast / ShowStatus / ShowOrderStatus / EMIT` хэвээр ажиллана (alias).
187
+ - `notification._3RD.*` хасагдсан.
package/dist/auth.d.ts CHANGED
@@ -1,8 +1,59 @@
1
+ import { ClientInfo, PermissionGroup, Result, TokenData } from "./types";
2
+ export declare class AuthState {
3
+ accessToken: string;
4
+ refreshToken: string;
5
+ /** ms epoch */
6
+ accessExpiresAt: number;
7
+ refreshExpiresAt: number;
8
+ permissions: string[];
9
+ lastError: string;
10
+ pending: Promise<string> | null;
11
+ get token(): string;
12
+ isAccessValid(): boolean;
13
+ isRefreshValid(): boolean;
14
+ apply(d: TokenData): void;
15
+ clear(): void;
16
+ /** Зэрэг олон хүсэлт ирэхэд нэг л удаа token авна */
17
+ single(fn: () => Promise<string>): Promise<string>;
18
+ }
19
+ export declare const authState: AuthState;
20
+ /** client_id + client_secret → шинэ token хос */
21
+ export declare const login: () => Promise<Result<TokenData>>;
22
+ /** refresh_token → шинэ token хос (хуучин refresh хүчингүй болно) */
23
+ export declare const refresh: () => Promise<Result<TokenData>>;
24
+ /**
25
+ * Хүчинтэй access token буцаана (cache → refresh → login).
26
+ * Бусад бүх API функц үүнийг автоматаар дуудна.
27
+ */
1
28
  export declare const getToken: () => Promise<string>;
29
+ /** Одоогийн refresh token-ийг серверт хүчингүй болгоод cache цэвэрлэнэ */
30
+ export declare const revoke: () => Promise<Result<null>>;
31
+ /** Client-ийн мэдээлэл + олгогдсон permission */
32
+ export declare const me: () => Promise<Result<ClientInfo>>;
33
+ /** Auth.mn-ийн бүх боломжит permission (public) */
34
+ export declare const permissions: () => Promise<Result<PermissionGroup[]>>;
35
+ /** Энэ client тухайн эрхтэй эсэх (сүүлийн token-оос) */
36
+ export declare const hasPermission: (perm: string) => boolean;
37
+ /**
38
+ * @deprecated v1.0.0-ээс: email/password-оор нэвтрэх боломжгүй болсон.
39
+ * setAuth({ client_id, client_secret }) ашиглана уу. Одоо getToken()-ийг дуудна.
40
+ */
41
+ export declare const TOKEN: (_legacy?: {
42
+ username: string;
43
+ password: string;
44
+ }) => Promise<string>;
2
45
  declare const _default: {
3
- TOKEN: (auth: {
46
+ getToken: () => Promise<string>;
47
+ login: () => Promise<Result<TokenData>>;
48
+ refresh: () => Promise<Result<TokenData>>;
49
+ revoke: () => Promise<Result<null>>;
50
+ me: () => Promise<Result<ClientInfo>>;
51
+ permissions: () => Promise<Result<PermissionGroup[]>>;
52
+ hasPermission: (perm: string) => boolean;
53
+ TOKEN: (_legacy?: {
4
54
  username: string;
5
55
  password: string;
6
56
  }) => Promise<string>;
57
+ state: AuthState;
7
58
  };
8
59
  export default _default;
package/dist/auth.js CHANGED
@@ -1,95 +1,147 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getToken = void 0;
4
- const _1 = require("./");
5
- const axios_master_1 = require("axios-master");
6
- const TOKEN = async (auth) => {
7
- try {
8
- const response = await (0, axios_master_1.axiosMasterLogger)({
9
- method: "POST",
10
- url: `${_1.config.hosts.MAIN}/main/v1/auth/client/login`,
11
- headers: {
12
- "Content-Type": "application/json"
13
- },
14
- data: {
15
- email: auth.username,
16
- password: auth.password
17
- }
18
- }, {
19
- name: "Auth.mn Token",
20
- timeout: 20000,
21
- logger: (data) => {
22
- if (_1.config.logger) {
23
- console.log({
24
- time: data.json.time,
25
- request: data.json.request,
26
- response: `${data.json.response}`,
27
- responseBody: data.json.responseBody,
28
- statusCode: data.json.statusCode
29
- });
30
- }
31
- }
32
- });
33
- if (response === null || response === void 0 ? void 0 : response.token) {
34
- _1.config.token = response === null || response === void 0 ? void 0 : response.token;
35
- return response === null || response === void 0 ? void 0 : response.token;
36
- }
37
- return "";
3
+ exports.TOKEN = exports.hasPermission = exports.permissions = exports.me = exports.revoke = exports.getToken = exports.refresh = exports.login = exports.authState = exports.AuthState = void 0;
4
+ const index_1 = require("./index");
5
+ const request_1 = require("./request");
6
+ /** Хугацаа дуусахаас хэдэн секундын өмнө refresh хийх */
7
+ const REFRESH_MARGIN_SEC = 60;
8
+ class AuthState {
9
+ constructor() {
10
+ this.accessToken = "";
11
+ this.refreshToken = "";
12
+ /** ms epoch */
13
+ this.accessExpiresAt = 0;
14
+ this.refreshExpiresAt = 0;
15
+ this.permissions = [];
16
+ this.lastError = "";
17
+ this.pending = null;
38
18
  }
39
- catch (error) {
40
- const axiosError = error;
41
- if (axiosError.data) {
42
- console.log(axiosError.data);
43
- }
44
- else {
45
- console.error("Token Request Failed:", axiosError);
19
+ get token() {
20
+ return this.accessToken;
21
+ }
22
+ isAccessValid() {
23
+ return !!this.accessToken && Date.now() < this.accessExpiresAt - REFRESH_MARGIN_SEC * 1000;
24
+ }
25
+ isRefreshValid() {
26
+ return !!this.refreshToken && Date.now() < this.refreshExpiresAt - 5000;
27
+ }
28
+ apply(d) {
29
+ this.accessToken = d.access_token;
30
+ this.refreshToken = d.refresh_token;
31
+ this.accessExpiresAt = Date.now() + d.expires_in * 1000;
32
+ this.refreshExpiresAt = Date.now() + d.refresh_expires_in * 1000;
33
+ this.permissions = d.permissions || [];
34
+ this.lastError = "";
35
+ }
36
+ clear() {
37
+ this.accessToken = "";
38
+ this.refreshToken = "";
39
+ this.accessExpiresAt = 0;
40
+ this.refreshExpiresAt = 0;
41
+ this.permissions = [];
42
+ }
43
+ /** Зэрэг олон хүсэлт ирэхэд нэг л удаа token авна */
44
+ single(fn) {
45
+ if (!this.pending) {
46
+ this.pending = fn().finally(() => (this.pending = null));
46
47
  }
47
- return "";
48
+ return this.pending;
49
+ }
50
+ }
51
+ exports.AuthState = AuthState;
52
+ exports.authState = new AuthState();
53
+ /** client_id + client_secret → шинэ token хос */
54
+ const login = async () => {
55
+ var _a;
56
+ if (!index_1.config.auth.client_id || !index_1.config.auth.client_secret) {
57
+ exports.authState.lastError = "client_id / client_secret тохируулаагүй байна (setAuth).";
58
+ return { success: false, message: exports.authState.lastError, data: null, status: 0 };
48
59
  }
60
+ const res = await (0, request_1.rawRequest)({
61
+ method: "POST",
62
+ path: "/main/v1/auth/client/token",
63
+ name: "client token",
64
+ data: { client_id: index_1.config.auth.client_id, client_secret: index_1.config.auth.client_secret }
65
+ });
66
+ if (res.success && ((_a = res.data) === null || _a === void 0 ? void 0 : _a.access_token))
67
+ exports.authState.apply(res.data);
68
+ else
69
+ exports.authState.lastError = res.message;
70
+ return res;
49
71
  };
50
- const getToken = async () => {
51
- try {
52
- const response = await (0, axios_master_1.axiosMasterLogger)({
53
- method: "POST",
54
- url: `${_1.config.hosts.MAIN}/main/v1/auth/client/login`,
55
- headers: {
56
- "Content-Type": "application/json"
57
- },
58
- data: {
59
- email: _1.config.auth.username,
60
- password: _1.config.auth.password
61
- }
62
- }, {
63
- name: "Auth.mn Token",
64
- timeout: 20000,
65
- logger: (data) => {
66
- if (_1.config.logger) {
67
- console.log({
68
- time: data.json.time,
69
- request: data.json.request,
70
- response: `${data.json.response}`,
71
- responseBody: data.json.responseBody,
72
- statusCode: data.json.statusCode
73
- });
74
- }
75
- }
76
- });
77
- if (response === null || response === void 0 ? void 0 : response.token) {
78
- _1.config.token = response === null || response === void 0 ? void 0 : response.token;
79
- return response === null || response === void 0 ? void 0 : response.token;
80
- }
81
- return "";
72
+ exports.login = login;
73
+ /** refresh_token → шинэ token хос (хуучин refresh хүчингүй болно) */
74
+ const refresh = async () => {
75
+ var _a;
76
+ if (!exports.authState.isRefreshValid()) {
77
+ return { success: false, message: "refresh token алга эсвэл хугацаа дууссан", data: null, status: 0 };
82
78
  }
83
- catch (error) {
84
- const axiosError = error;
85
- if (axiosError.data) {
86
- console.log(axiosError.data);
87
- }
88
- else {
89
- console.error("Token Request Failed:", axiosError);
90
- }
91
- return "";
79
+ const res = await (0, request_1.rawRequest)({
80
+ method: "POST",
81
+ path: "/main/v1/auth/client/token/refresh",
82
+ name: "client token refresh",
83
+ data: { refresh_token: exports.authState.refreshToken }
84
+ });
85
+ if (res.success && ((_a = res.data) === null || _a === void 0 ? void 0 : _a.access_token))
86
+ exports.authState.apply(res.data);
87
+ else {
88
+ exports.authState.clear();
89
+ exports.authState.lastError = res.message;
92
90
  }
91
+ return res;
93
92
  };
93
+ exports.refresh = refresh;
94
+ /**
95
+ * Хүчинтэй access token буцаана (cache → refresh → login).
96
+ * Бусад бүх API функц үүнийг автоматаар дуудна.
97
+ */
98
+ const getToken = () => exports.authState.single(async () => {
99
+ if (exports.authState.isAccessValid())
100
+ return exports.authState.accessToken;
101
+ if (exports.authState.isRefreshValid()) {
102
+ const r = await (0, exports.refresh)();
103
+ if (r.success)
104
+ return exports.authState.accessToken;
105
+ }
106
+ const l = await (0, exports.login)();
107
+ return l.success ? exports.authState.accessToken : "";
108
+ });
94
109
  exports.getToken = getToken;
95
- exports.default = { TOKEN };
110
+ /** Одоогийн refresh token-ийг серверт хүчингүй болгоод cache цэвэрлэнэ */
111
+ const revoke = async () => {
112
+ const rt = exports.authState.refreshToken;
113
+ exports.authState.clear();
114
+ if (!rt)
115
+ return { success: true, message: "token алга", data: null };
116
+ return (0, request_1.rawRequest)({
117
+ method: "POST",
118
+ path: "/main/v1/auth/client/token/revoke",
119
+ name: "client token revoke",
120
+ data: { refresh_token: rt }
121
+ });
122
+ };
123
+ exports.revoke = revoke;
124
+ /** Client-ийн мэдээлэл + олгогдсон permission */
125
+ const me = () => (0, request_1.request)({ method: "GET", path: "/main/v1/auth/client/me", name: "client me" });
126
+ exports.me = me;
127
+ /** Auth.mn-ийн бүх боломжит permission (public) */
128
+ const permissions = () => (0, request_1.rawRequest)({ method: "GET", path: "/main/v1/auth/client/permissions", name: "permissions" });
129
+ exports.permissions = permissions;
130
+ /** Энэ client тухайн эрхтэй эсэх (сүүлийн token-оос) */
131
+ const hasPermission = (perm) => {
132
+ const p = exports.authState.permissions;
133
+ return p.includes("*") || p.includes(perm) || p.includes(`${perm.split(".")[0]}.*`);
134
+ };
135
+ exports.hasPermission = hasPermission;
136
+ /**
137
+ * @deprecated v1.0.0-ээс: email/password-оор нэвтрэх боломжгүй болсон.
138
+ * setAuth({ client_id, client_secret }) ашиглана уу. Одоо getToken()-ийг дуудна.
139
+ */
140
+ const TOKEN = async (_legacy) => {
141
+ if (_legacy && index_1.config.logger) {
142
+ console.warn("[auth-mn] auth.TOKEN({username,password}) хуучирсан — setAuth({ client_id, client_secret }) ашиглана уу.");
143
+ }
144
+ return (0, exports.getToken)();
145
+ };
146
+ exports.TOKEN = TOKEN;
147
+ exports.default = { getToken: exports.getToken, login: exports.login, refresh: exports.refresh, revoke: exports.revoke, me: exports.me, permissions: exports.permissions, hasPermission: exports.hasPermission, TOKEN: exports.TOKEN, state: exports.authState };