@pubinfo-nightly/module-auth 2025.11.14

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.
@@ -0,0 +1,51 @@
1
+ import { RouteLocationRaw } from '../../../node_modules/.pnpm/vue-router@4.5.1_vue@3.5.17_typescript@5.8.3_/node_modules/vue-router';
2
+ export type Recordable = Record<string, any>;
3
+ export type Fn<T = any, K = any> = (params?: Recordable & T) => Promise<K> | K;
4
+ export type FnApi<T = any> = (params: Recordable & T, baseURL: string) => Promise<any>;
5
+ export interface AuthOptions {
6
+ /** 登录后默认重定向至 */
7
+ redirectTo?: RouteLocationRaw | Fn;
8
+ /** 页面配置 */
9
+ pages?: {
10
+ /** 默认登录页 */
11
+ signIn?: RouteLocationRaw;
12
+ };
13
+ /** 接口的baseURL */
14
+ baseURL: string;
15
+ providers?: ProviderOptions[];
16
+ }
17
+ export interface InternalContext {
18
+ /** 接口的baseURL */
19
+ baseURL: string;
20
+ providers?: ProviderOptions[];
21
+ }
22
+ /** 第三方类型 */
23
+ export type ThirdParty = string;
24
+ export type ProviderUserOptions = Partial<ProviderOptions>;
25
+ export interface ProviderOptions {
26
+ /** 唯一值 */
27
+ id: string;
28
+ /** 名称 */
29
+ name: string;
30
+ /**
31
+ * 协议类型
32
+ * - `oauth` 第三方授权登录
33
+ * - `cas` 统一账号登录
34
+ * - `custom` 自定义
35
+ */
36
+ type: 'oauth' | 'cas' | 'custom';
37
+ /** 授权登录地址 */
38
+ signIn?: Authorization | Fn;
39
+ /** 授权登录接口 */
40
+ authenticate?: string | FnApi;
41
+ /** 初始化二维码 */
42
+ initQRCode?: (url: string, params: {
43
+ id: string;
44
+ } & Recordable) => Promise<void> | void;
45
+ }
46
+ export interface Authorization {
47
+ /** 授权登录地址 */
48
+ url: string;
49
+ /** 授权登录参数 */
50
+ params?: Recordable;
51
+ }
@@ -0,0 +1,11 @@
1
+ export declare const PageAuth: import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').DefineComponent<import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').ExtractPropTypes<{
2
+ type: StringConstructor;
3
+ redirectTo: FunctionConstructor;
4
+ authenticate: FunctionConstructor;
5
+ }>, () => import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').VNode<import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').RendererNode, import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').RendererElement, {
6
+ [key: string]: any;
7
+ }>, {}, {}, {}, import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').ComponentOptionsMixin, import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').ComponentOptionsMixin, {}, string, import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').PublicProps, Readonly<import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').ExtractPropTypes<{
8
+ type: StringConstructor;
9
+ redirectTo: FunctionConstructor;
10
+ authenticate: FunctionConstructor;
11
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import('../../../../node_modules/.pnpm/vue@3.5.17_typescript@5.8.3/node_modules/vue').ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,15 @@
1
+ import { ProviderOptions, ProviderUserOptions } from '../interface';
2
+ export interface FourAOptions extends ProviderUserOptions {
3
+ /** `client_id` */
4
+ clientId?: string;
5
+ /** `redirect_uri` */
6
+ redirectUri?: string;
7
+ }
8
+ /**
9
+ * `4A 统一认证`
10
+ *
11
+ * - 携带 `client_id` 和 `redirect_uri`,前往 [4A认证中心](http://134.108.76.137:7001/index) 进行登录;
12
+ * - 成功登录后,4A认证中心会回调 `redirect_uri` 并携带参数;
13
+ * - 当前 `provider` 将获取参数值并调用 `/bs/loginBy4a` 完成登录认证。
14
+ */
15
+ export default function FourA(options: FourAOptions): ProviderOptions;
@@ -0,0 +1,21 @@
1
+ function i(e) {
2
+ const { clientId: t, redirectUri: r } = e;
3
+ return {
4
+ id: "4A",
5
+ name: "4A登录",
6
+ type: "oauth",
7
+ signIn: {
8
+ url: "http://134.108.76.137:7001/index",
9
+ params: {
10
+ response_type: "code",
11
+ client_id: t,
12
+ redirect_uri: r
13
+ }
14
+ },
15
+ authenticate: "/bs/loginBy4a",
16
+ ...e
17
+ };
18
+ }
19
+ export {
20
+ i as default
21
+ };
@@ -0,0 +1,7 @@
1
+ import { ProviderOptions, ProviderUserOptions } from '../interface';
2
+ export interface CredentialsOptions extends ProviderUserOptions {
3
+ }
4
+ /**
5
+ * 凭证登录
6
+ */
7
+ export default function Credentials(options?: CredentialsOptions): ProviderOptions;
@@ -0,0 +1,11 @@
1
+ function t(e) {
2
+ return {
3
+ id: "credentials",
4
+ name: "凭证登录",
5
+ type: "custom",
6
+ ...e
7
+ };
8
+ }
9
+ export {
10
+ t as default
11
+ };
@@ -0,0 +1,17 @@
1
+ import { ProviderOptions, ProviderUserOptions } from '../interface';
2
+ export interface DingZJOptions extends ProviderUserOptions {
3
+ /**
4
+ * 登录方式
5
+ * - `redirect` 跳转浙政钉的扫码页面
6
+ * - `embed` 内嵌浙政钉的登录二维码
7
+ */
8
+ mode: 'redirect' | 'embed';
9
+ /** `client_id` */
10
+ clientId?: string;
11
+ /** `redirect_uri` */
12
+ redirectUri?: string;
13
+ }
14
+ /**
15
+ * 浙政钉扫码登录
16
+ */
17
+ export default function DingZJ(options: DingZJOptions): ProviderOptions;
@@ -0,0 +1,49 @@
1
+ function g(e) {
2
+ const { clientId: i, redirectUri: n } = e, a = {
3
+ /** 跳转外部页面 */
4
+ redirect: {
5
+ url: "https://openplatform-pro.ding.zj.gov.cn/oauth2/auth.htm",
6
+ params: {
7
+ response_type: "code",
8
+ client_id: i,
9
+ redirect_uri: n,
10
+ scope: "get_user_info",
11
+ authType: "QRCODE"
12
+ }
13
+ },
14
+ /** 内嵌二维码 */
15
+ embed: {
16
+ url: "https://login-pro.ding.zj.gov.cn/oauth2/auth.htm",
17
+ params: {
18
+ response_type: "code",
19
+ client_id: i,
20
+ redirect_uri: n,
21
+ scope: "get_user_info",
22
+ authType: "QRCODE",
23
+ embedMode: "true"
24
+ }
25
+ }
26
+ }, o = (c, t) => {
27
+ if (!document.getElementById(t.id))
28
+ throw new Error(`未找到id为${t.id}的DOM元素`);
29
+ const r = document.createElement("iframe");
30
+ r.src = c, r.width = "200", r.height = "200", document.appendChild(r), window.addEventListener("message", (s) => {
31
+ if (t.redirectUri) {
32
+ const d = new URL(t.redirectUri);
33
+ d.search = new URLSearchParams(s.data).toString(), window.location.href = d.toString();
34
+ }
35
+ });
36
+ };
37
+ return {
38
+ id: "ding-zj",
39
+ name: "浙政钉登录",
40
+ type: "oauth",
41
+ signIn: a[e.mode],
42
+ authenticate: "/bs/loginByDingZJ",
43
+ initQRCode: e.mode === "embed" ? o : void 0,
44
+ ...e
45
+ };
46
+ }
47
+ export {
48
+ g as default
49
+ };
@@ -0,0 +1,19 @@
1
+ import { ProviderOptions, ProviderUserOptions } from '../interface';
2
+ export interface SsoOptions extends ProviderUserOptions {
3
+ /**
4
+ * 登录类型,根据调用 `/app/oauth/generateCode` 接口时传递的 `grantType` 参数值判断:
5
+ *
6
+ * - `OAUTH2` -> `defaultOauth2`
7
+ * - `SIMPLE_ENCRYPT` -> `encrypt`
8
+ *
9
+ * @default `defaultOauth2`
10
+ */
11
+ loginType?: 'defaultOauth2' | 'encrypt';
12
+ }
13
+ /**
14
+ * `SSO 统一账号登录`
15
+ *
16
+ * - 主系统调用 `/app/oauth/generateCode?appKey=xxx` 获取 `code`,并作为参数跳转至子系统;
17
+ * - 当前 `provider` 将获取 `code` 并调用 `/auth/ssoLogin` 完成登录认证。
18
+ */
19
+ export default function Sso(options?: SsoOptions): ProviderOptions;
@@ -0,0 +1,23 @@
1
+ function o(t = {}) {
2
+ const { loginType: e = "defaultOauth2" } = t;
3
+ return {
4
+ id: "sso",
5
+ name: "SSO统一账号登录",
6
+ type: "custom",
7
+ authenticate: async (a, n) => await (await fetch(
8
+ `${n}/auth/ssoLogin`,
9
+ {
10
+ headers: { "Content-Type": "application/json" },
11
+ method: "POST",
12
+ body: JSON.stringify({
13
+ ...a,
14
+ loginType: e
15
+ })
16
+ }
17
+ )).json(),
18
+ ...t
19
+ };
20
+ }
21
+ export {
22
+ o as default
23
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@pubinfo-nightly/module-auth",
3
+ "type": "module",
4
+ "version": "2025.11.14",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ },
10
+ "./providers/*": {
11
+ "types": "./dist/providers/*.d.ts",
12
+ "default": "./dist/providers/*.js"
13
+ }
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "engines": {
23
+ "node": "^20.19.0 || >=22.12.0"
24
+ },
25
+ "peerDependencies": {
26
+ "pubinfo-nightly": "2025.11.14"
27
+ },
28
+ "devDependencies": {
29
+ "pubinfo-nightly": "2025.11.14"
30
+ },
31
+ "scripts": {
32
+ "dev": "pubinfo build --watch",
33
+ "build": "pubinfo build",
34
+ "generate": "pubinfo generate"
35
+ }
36
+ }
Binary file
@@ -0,0 +1,16 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import FourAPNG from '@/assets/images/4A.png';
3
+ import { RedirectLogin } from '../RedirectLogin';
4
+
5
+ export const LoginWithFourA = defineComponent({
6
+ name: 'LoginWithFourA',
7
+ props: {
8
+ color: {
9
+ type: String,
10
+ default: '#1677FF',
11
+ },
12
+ },
13
+ setup(props) {
14
+ return () => h(RedirectLogin, { id: '4A', src: FourAPNG, color: props.color });
15
+ },
16
+ });
@@ -0,0 +1,43 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import { signIn } from '@/core';
3
+ import { getProvider } from '@/helper';
4
+
5
+ export const RedirectLogin = defineComponent({
6
+ name: 'RedirectLogin',
7
+ props: {
8
+ id: String,
9
+ src: String,
10
+ color: String,
11
+ },
12
+ setup(props) {
13
+ const provider = getProvider(props.id);
14
+ return () => {
15
+ return h('div', [
16
+ h(
17
+ 'div',
18
+ { style: { padding: '3rem' } },
19
+ h('img', { src: props.src, class: 'w-full' }),
20
+ ),
21
+ h(
22
+ 'button',
23
+ {
24
+ style: {
25
+ width: '100%',
26
+ color: '#ffffff',
27
+ cursor: 'pointer',
28
+ border: '0',
29
+ borderRadius: '0.25rem',
30
+ padding: '0.75rem 1.25rem',
31
+ fontSize: '0.875rem',
32
+ lineHeight: '1.25rem',
33
+ letterSpacing: '0.1em',
34
+ backgroundColor: props.color,
35
+ },
36
+ onClick: () => signIn(provider.id),
37
+ },
38
+ `前往${provider.name}`,
39
+ ),
40
+ ]);
41
+ };
42
+ },
43
+ });
package/src/context.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { InternalContext } from './interface';
2
+ import { createContext } from 'pubinfo-nightly';
3
+
4
+ export const ctx = createContext<InternalContext>('auth');
package/src/core.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { Recordable, ThirdParty } from './interface';
2
+ import { ctx } from './context';
3
+ import { createUrl, createUrlByProvider, getProvider } from './helper';
4
+
5
+ /**
6
+ * 前往登录
7
+ * @param id 唯一值
8
+ * @param options 登录时需要传递的参数
9
+ */
10
+ export async function signIn(id: ThirdParty, options?: Recordable) {
11
+ const provider = getProvider(id);
12
+
13
+ if (typeof provider.signIn === 'function') {
14
+ return await provider.signIn(options);
15
+ }
16
+
17
+ if (['oauth', 'custom'].includes(provider.type)) {
18
+ const url = createUrlByProvider(provider);
19
+ window.location.replace(url);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * 生成二维码
25
+ * @param id 第三方的唯一值
26
+ * @param options 生成二维码需要传递的参数
27
+ * @param options.id `getElementById`中指定的`id`
28
+ */
29
+ export function renderQRCode(id: ThirdParty, options: { id: string } & Recordable) {
30
+ const provider = getProvider(id);
31
+
32
+ if (['oauth', 'custom'].includes(provider.type)) {
33
+ const url = createUrlByProvider(provider);
34
+ provider.initQRCode?.(url, options);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 认证
40
+ * @param id 第三方的唯一值
41
+ */
42
+ export async function authenticate(id: ThirdParty) {
43
+ const provider = getProvider(id);
44
+ const { baseURL } = ctx.use();
45
+
46
+ // TODO 处理hash路由中的#
47
+ // 获取URL上的参数
48
+ const qs = window.location.href.split('?')?.[1] ?? '';
49
+ const params = new URLSearchParams(qs);
50
+ const paramsObject = Object.fromEntries(params.entries());
51
+
52
+ if (typeof provider.authenticate === 'function') {
53
+ return await provider.authenticate(paramsObject, baseURL);
54
+ }
55
+
56
+ if (['oauth', 'cas', 'custom'].includes(provider.type)) {
57
+ const url = createUrl(`${baseURL}${provider.authenticate}`, paramsObject);
58
+ const response = await fetch(url);
59
+ return await response.json();
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 重定向至统一登录页
65
+ */
66
+ export function redirect() {
67
+ const { providers } = ctx.use();
68
+ const provider = providers?.find(provider => provider.type === 'cas');
69
+ if (!provider) {
70
+ return;
71
+ }
72
+
73
+ const url = createUrlByProvider(provider);
74
+ window.location.replace(url);
75
+ }
package/src/helper.ts ADDED
@@ -0,0 +1,77 @@
1
+ import type { ProviderUserOptions, Recordable, ThirdParty } from './interface';
2
+ import { ctx } from './context';
3
+
4
+ /**
5
+ * 获取指定的 `Provider`
6
+ * @param id
7
+ */
8
+ export function getProvider(id?: ThirdParty) {
9
+ const { providers } = ctx.use();
10
+ const provider = id ? providers?.find(provider => provider.id === id) : providers?.[0];
11
+ if (!provider) {
12
+ throw new Error(`Provider '${id}' is not found.`);
13
+ }
14
+ return provider;
15
+ }
16
+
17
+ /**
18
+ * 构建第三方授权登录地址
19
+ * @param provider
20
+ */
21
+ export function createUrlByProvider(provider: ProviderUserOptions) {
22
+ const { signIn } = provider;
23
+
24
+ if (typeof signIn === 'function') {
25
+ return '';
26
+ }
27
+
28
+ return createUrl(signIn?.url, signIn?.params);
29
+ }
30
+
31
+ /**
32
+ * 构建携带 `params` 的 URL
33
+ * @param url
34
+ * @param params
35
+ */
36
+ export function createUrl(url: string = '', params: Recordable = {}) {
37
+ const _url = new URL(url);
38
+ const _params = new URLSearchParams(_url.search);
39
+
40
+ // 合并参数
41
+ for (const [key, value] of Object.entries(params)) {
42
+ _params.set(key, value);
43
+ }
44
+
45
+ _url.search = _params.toString();
46
+
47
+ return _url.toString();
48
+ }
49
+
50
+ /**
51
+ * 读取 `URL` 上的 `params` 参数
52
+ * @param url
53
+ */
54
+ export function readUrlParams(url?: string): URLSearchParams {
55
+ const { href, search, hash } = url ? new URL(url) : window.location;
56
+
57
+ // 1.优先从 search(history 模式)拿
58
+ if (search) {
59
+ return new URLSearchParams(search);
60
+ };
61
+
62
+ // 2.再尝试从 hash(hash 模式)里拿
63
+ // 可能是 /#/callback?code=xxx
64
+ const hashIndex = hash.indexOf('?');
65
+ if (hashIndex !== -1) {
66
+ return new URLSearchParams(hash.slice(hashIndex));
67
+ }
68
+
69
+ // 3.fallback:尝试整个 URL
70
+ const qsIndex = href.indexOf('?');
71
+ if (qsIndex !== -1) {
72
+ return new URLSearchParams(href.slice(qsIndex));
73
+ }
74
+
75
+ // 4.否则返回空
76
+ return new URLSearchParams();
77
+ }
package/src/index.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { ModuleOptions } from 'pubinfo-nightly';
2
+ import type { RouteRecordRaw } from 'vue-router';
3
+ import type { AuthOptions } from './interface';
4
+ import { cleanup } from 'pubinfo-nightly';
5
+ import { ctx } from './context';
6
+ import { authenticate, redirect } from './core';
7
+ import { PageAuth } from './pages/auth';
8
+
9
+ export function auth(options: AuthOptions): ModuleOptions {
10
+ const {
11
+ redirectTo = '/',
12
+ pages = {},
13
+ baseURL,
14
+ providers,
15
+ } = options;
16
+ const { signIn = '/login' } = pages;
17
+
18
+ ctx.set({ baseURL, providers });
19
+
20
+ return {
21
+ name: 'pubinfo-nightly:auth',
22
+ enforce: 'pre',
23
+ setup(ctx) {
24
+ const { router } = ctx;
25
+
26
+ const ROUTE_AUTH: RouteRecordRaw = {
27
+ path: '/auth/:type',
28
+ name: 'Auth',
29
+ component: PageAuth,
30
+ meta: {
31
+ whiteList: true,
32
+ title: '授权登录',
33
+ },
34
+ props: route => ({
35
+ type: route.params.type,
36
+ authenticate,
37
+ redirectTo() {
38
+ if (typeof redirectTo === 'function') {
39
+ redirectTo();
40
+ return;
41
+ }
42
+
43
+ router.push(redirectTo);
44
+ },
45
+ }),
46
+ };
47
+
48
+ router.beforeEach((to) => {
49
+ // 注册静态页面
50
+ if (!router.hasRoute(ROUTE_AUTH.name!)) {
51
+ router.addRoute(ROUTE_AUTH);
52
+ return to.fullPath;
53
+ }
54
+
55
+ // 前往单点登录前,清空登录状态
56
+ if (to.name === ROUTE_AUTH.name) {
57
+ cleanup();
58
+ }
59
+
60
+ // 前往登录前,重定向至统一登录页(若有)
61
+ if (to.path === signIn) {
62
+ redirect();
63
+ }
64
+ });
65
+ },
66
+ };
67
+ }
68
+
69
+ export { LoginWithFourA } from './components/LoginWithFourA';
70
+
71
+ export {
72
+ authenticate,
73
+ redirect,
74
+ renderQRCode,
75
+ signIn,
76
+ } from './core';
77
+
78
+ export * from './interface';
@@ -0,0 +1,65 @@
1
+ import type { RouteLocationRaw } from 'vue-router';
2
+
3
+ export type Recordable = Record<string, any>;
4
+ export type Fn<T = any, K = any> = (params?: Recordable & T) => Promise<K> | K;
5
+ export type FnApi<T = any> = (params: Recordable & T, baseURL: string) => Promise<any>;
6
+
7
+ export interface AuthOptions {
8
+ /** 登录后默认重定向至 */
9
+ redirectTo?: RouteLocationRaw | Fn
10
+
11
+ /** 页面配置 */
12
+ pages?: {
13
+ /** 默认登录页 */
14
+ signIn?: RouteLocationRaw
15
+ }
16
+
17
+ /** 接口的baseURL */
18
+ baseURL: string
19
+
20
+ providers?: ProviderOptions[]
21
+ }
22
+
23
+ export interface InternalContext {
24
+ /** 接口的baseURL */
25
+ baseURL: string
26
+
27
+ providers?: ProviderOptions[]
28
+ }
29
+
30
+ /** 第三方类型 */
31
+ export type ThirdParty = string;
32
+ export type ProviderUserOptions = Partial<ProviderOptions>;
33
+
34
+ export interface ProviderOptions {
35
+ /** 唯一值 */
36
+ id: string
37
+
38
+ /** 名称 */
39
+ name: string
40
+
41
+ /**
42
+ * 协议类型
43
+ * - `oauth` 第三方授权登录
44
+ * - `cas` 统一账号登录
45
+ * - `custom` 自定义
46
+ */
47
+ type: 'oauth' | 'cas' | 'custom'
48
+
49
+ /** 授权登录地址 */
50
+ signIn?: Authorization | Fn
51
+
52
+ /** 授权登录接口 */
53
+ authenticate?: string | FnApi
54
+
55
+ /** 初始化二维码 */
56
+ initQRCode?: (url: string, params: { id: string } & Recordable) => Promise<void> | void
57
+ }
58
+
59
+ export interface Authorization {
60
+ /** 授权登录地址 */
61
+ url: string
62
+
63
+ /** 授权登录参数 */
64
+ params?: Recordable
65
+ }