@strivacity/sdk-nuxt 3.0.3 → 4.0.0-beta.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/.nuxt/app-component.mjs +1 -1
- package/.nuxt/app.config.mjs +21 -0
- package/.nuxt/component-names.mjs +1 -1
- package/.nuxt/components.d.ts +48 -48
- package/.nuxt/components.islands.mjs +6 -2
- package/.nuxt/error-component.mjs +1 -1
- package/.nuxt/fetch.d.ts +1 -0
- package/.nuxt/fetch.mjs +4 -3
- package/.nuxt/imports.d.ts +15 -5
- package/.nuxt/imports.mjs +15 -5
- package/.nuxt/island-renderer.mjs +2 -0
- package/.nuxt/middleware.mjs +3 -1
- package/.nuxt/nuxt.config.mjs +103 -0
- package/.nuxt/nuxt.d.ts +3 -4
- package/.nuxt/nuxt.node.d.ts +4 -4
- package/.nuxt/nuxt.shared.d.ts +3 -1
- package/.nuxt/plugins.client.mjs +10 -14
- package/.nuxt/plugins.server.mjs +6 -6
- package/.nuxt/root-component.mjs +1 -1
- package/.nuxt/route-rules.mjs +22 -0
- package/.nuxt/test-component-wrapper.mjs +1 -1
- package/.nuxt/tsconfig.app.json +211 -0
- package/.nuxt/tsconfig.json +212 -0
- package/.nuxt/tsconfig.node.json +117 -0
- package/.nuxt/tsconfig.server.json +161 -0
- package/.nuxt/tsconfig.shared.json +165 -0
- package/.nuxt/types/app.config.d.ts +35 -0
- package/.nuxt/types/components.d.ts +48 -48
- package/.nuxt/types/imports.d.ts +1 -1
- package/.nuxt/types/layouts.d.ts +16 -0
- package/.nuxt/types/modules.d.ts +20 -20
- package/.nuxt/types/nitro-imports.d.ts +9 -4
- package/.nuxt/types/nitro-layouts.d.ts +17 -0
- package/.nuxt/types/nitro-nuxt.d.ts +19 -5
- package/.nuxt/types/nitro-routes.d.ts +25 -1
- package/.nuxt/types/plugins.d.ts +8 -9
- package/.nuxt/types/runtime-config.d.ts +6 -12
- package/.nuxt/types/shared-app.config.d.ts +28 -0
- package/.nuxt/types/shared-imports.d.ts +2 -0
- package/.nuxt/unhead-options.mjs +2 -1
- package/.nuxt/unhead.config.mjs +1 -0
- package/CHANGELOG.md +10 -14
- package/README.md +1577 -406
- package/build.config.ts +5 -0
- package/dist/module.d.mts +3 -193
- package/dist/module.json +2 -2
- package/dist/module.mjs +78 -57
- package/dist/module.mjs.map +1 -1
- package/dist/runtime/composables/use-native-login-context.d.ts +10 -0
- package/dist/runtime/composables/use-native-login-context.js +9 -0
- package/dist/runtime/composables/use-native-login.d.ts +10 -0
- package/dist/runtime/composables/use-native-login.js +143 -0
- package/dist/runtime/composables/use-session.d.ts +8 -0
- package/dist/runtime/composables/use-session.js +2 -0
- package/dist/runtime/composables/use-strivacity.d.ts +9 -0
- package/dist/runtime/composables/use-strivacity.js +77 -0
- package/dist/runtime/middleware/auth.server.d.ts +2 -0
- package/dist/runtime/middleware/auth.server.js +12 -0
- package/dist/runtime/server/api/auth/backchannel-logout.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/backchannel-logout.get.js +2 -0
- package/dist/runtime/server/api/auth/callback.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/callback.get.js +2 -0
- package/dist/runtime/server/api/auth/entry.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/entry.get.js +2 -0
- package/dist/runtime/server/api/auth/login.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/login.get.js +2 -0
- package/dist/runtime/server/api/auth/logout.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/logout.get.js +2 -0
- package/dist/runtime/server/api/auth/refresh.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/refresh.get.js +2 -0
- package/dist/runtime/server/api/auth/register.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/register.get.js +2 -0
- package/dist/runtime/server/api/auth/revoke.get.d.ts +2 -0
- package/dist/runtime/server/api/auth/revoke.get.js +2 -0
- package/dist/runtime/server/composables/use-strivacity.d.ts +2 -0
- package/dist/runtime/server/composables/use-strivacity.js +9 -0
- package/dist/runtime/server/plugins/auth.server.d.ts +10 -0
- package/dist/runtime/server/plugins/auth.server.js +66 -0
- package/dist/runtime/server/utils/helpers.d.ts +18 -0
- package/dist/runtime/server/utils/helpers.js +39 -0
- package/dist/runtime/storages/core.d.ts +1 -0
- package/dist/runtime/storages/core.js +13 -0
- package/dist/runtime/storages/default.d.ts +21 -0
- package/dist/runtime/storages/default.js +71 -0
- package/dist/runtime/types.d.ts +219 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/utils/core.d.ts +1 -0
- package/dist/runtime/utils/core.js +64 -0
- package/dist/runtime/utils/noop.d.ts +1 -0
- package/dist/runtime/utils/noop.js +3 -0
- package/dist/types.d.mts +4 -16
- package/eslint.config.mjs +5 -0
- package/nuxt.config.ts +5 -0
- package/package.json +31 -5
- package/project.json +44 -0
- package/src/module.ts +107 -0
- package/src/runtime/composables/use-native-login-context.ts +20 -0
- package/src/runtime/composables/use-native-login.ts +182 -0
- package/src/runtime/composables/use-session.ts +10 -0
- package/src/runtime/composables/use-strivacity.ts +95 -0
- package/src/runtime/middleware/auth.server.ts +16 -0
- package/src/runtime/server/api/auth/backchannel-logout.get.ts +3 -0
- package/src/runtime/server/api/auth/callback.get.ts +3 -0
- package/src/runtime/server/api/auth/entry.get.ts +3 -0
- package/src/runtime/server/api/auth/login.get.ts +3 -0
- package/src/runtime/server/api/auth/logout.get.ts +3 -0
- package/src/runtime/server/api/auth/refresh.get.ts +3 -0
- package/src/runtime/server/api/auth/register.get.ts +3 -0
- package/src/runtime/server/api/auth/revoke.get.ts +3 -0
- package/src/runtime/server/composables/use-strivacity.ts +13 -0
- package/src/runtime/server/plugins/auth.server.ts +84 -0
- package/src/runtime/server/utils/helpers.ts +67 -0
- package/src/runtime/storages/core.ts +13 -0
- package/src/runtime/storages/default.ts +111 -0
- package/src/runtime/types.ts +289 -0
- package/src/runtime/utils/core.ts +64 -0
- package/src/runtime/utils/noop.ts +3 -0
- package/testing/stubs/app.ts +6 -0
- package/testing/stubs/imports.ts +19 -0
- package/testing/stubs/options-httpClient.ts +8 -0
- package/testing/stubs/options-logging.ts +8 -0
- package/testing/stubs/options-stateStorage.ts +8 -0
- package/testing/stubs/options-storage.ts +8 -0
- package/testing/tests/composables.spec.ts +512 -0
- package/testing/tests/server/helpers.spec.ts +86 -0
- package/testing/tests/server/plugin.spec.ts +138 -0
- package/testing/tests/server/routes.spec.ts +34 -0
- package/testing/tests/server/use-strivacity.spec.ts +22 -0
- package/testing/tests/storages-default.spec.ts +151 -0
- package/testing/tests/storages.spec.ts +16 -0
- package/testing/tests/utils.spec.ts +10 -0
- package/testing/utils/common.ts +33 -0
- package/testing/utils/http.ts +58 -0
- package/tsconfig.json +3 -0
- package/vite.config.mts +20 -0
- package/.nuxt/strivacity-sdk-logging.d.ts +0 -13
- package/.nuxt/strivacity-sdk-logging.mjs +0 -1
- package/.nuxt/strivacity-sdk-storage.d.ts +0 -10
- package/.nuxt/strivacity-sdk-storage.mjs +0 -1
- package/dist/runtime/composables.d.ts +0 -12
- package/dist/runtime/composables.js +0 -83
- package/dist/runtime/login-renderer.vue +0 -174
- package/dist/runtime/login-renderer.vue.d.ts +0 -39
- /package/.nuxt/manifest/meta/{7ca435a8-f435-49ea-bcf2-c23ff70749f8.json → cb11df97-b0c9-4a91-9d4b-60c25c918599.json} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { NitroApp } from 'nitropack/types';
|
|
2
|
+
import type { H3Event } from 'h3';
|
|
3
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { createMockStorage, createServerOptions, getMockStorage } from '@strivacity/testing/mocks/sdk';
|
|
5
|
+
import { createFakeH3Event } from '../../utils/http';
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.resetModules();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.doUnmock('#imports');
|
|
13
|
+
vi.doUnmock('#strivacity-options-storage');
|
|
14
|
+
vi.doUnmock('#strivacity-options-stateStorage');
|
|
15
|
+
vi.doUnmock('#strivacity-options-logging');
|
|
16
|
+
vi.doUnmock('#strivacity-options-httpClient');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function createFakeNitroApp() {
|
|
20
|
+
const hook = vi.fn();
|
|
21
|
+
|
|
22
|
+
return { app: { hooks: { hook } } as unknown as NitroApp, hook };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function installPlugin(strivacityConfig: Record<string, unknown>) {
|
|
26
|
+
vi.doMock('#imports', () => ({ useRuntimeConfig: () => ({ strivacity: strivacityConfig }) }));
|
|
27
|
+
|
|
28
|
+
const plugin = (await import('../../../src/runtime/server/plugins/auth.server')).default;
|
|
29
|
+
const { app, hook } = createFakeNitroApp();
|
|
30
|
+
|
|
31
|
+
plugin(app);
|
|
32
|
+
|
|
33
|
+
// setupAuthPlugin() is fired via `void` (not awaited) and chains several dynamic imports
|
|
34
|
+
// (the four '#strivacity-options-*' factories) before it registers the hook, which on a cold
|
|
35
|
+
// module cache (every test resets it) can outlast a single microtask tick.
|
|
36
|
+
await vi.waitFor(() => expect(hook).toHaveBeenCalled());
|
|
37
|
+
const requestHook = hook.mock.calls[0]?.[1] as ((event: H3Event) => void) | undefined;
|
|
38
|
+
|
|
39
|
+
return { hook, requestHook };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('auth.server nitro plugin', () => {
|
|
43
|
+
test('registers a "request" hook that wires a fully-shaped sdk onto the event context', async () => {
|
|
44
|
+
const storage = createMockStorage();
|
|
45
|
+
const stateStorage = createMockStorage();
|
|
46
|
+
const { hook, requestHook } = await installPlugin(createServerOptions({ storage, stateStorage }));
|
|
47
|
+
|
|
48
|
+
expect(hook).toHaveBeenCalledWith('request', expect.any(Function));
|
|
49
|
+
|
|
50
|
+
const event = { context: {} } as unknown as H3Event;
|
|
51
|
+
requestHook!(event);
|
|
52
|
+
|
|
53
|
+
const sdk = event.context.strivacity.sdk;
|
|
54
|
+
expect(sdk.options).toBeDefined();
|
|
55
|
+
|
|
56
|
+
for (const method of [
|
|
57
|
+
'getSession',
|
|
58
|
+
'updateSession',
|
|
59
|
+
'refreshSession',
|
|
60
|
+
'revokeSession',
|
|
61
|
+
'getEntrySession',
|
|
62
|
+
'completeLogin',
|
|
63
|
+
'logout',
|
|
64
|
+
'handleLogin',
|
|
65
|
+
'handleRegister',
|
|
66
|
+
'handleCallback',
|
|
67
|
+
'handleRefresh',
|
|
68
|
+
'handleRevoke',
|
|
69
|
+
'handleEntry',
|
|
70
|
+
'handleLogout',
|
|
71
|
+
'handleBackChannelLogout',
|
|
72
|
+
'handler',
|
|
73
|
+
] as const) {
|
|
74
|
+
expect(typeof sdk[method]).toBe('function');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('delegates getSession/updateSession to the underlying base server sdk against the configured storage', async () => {
|
|
79
|
+
const storage = createMockStorage();
|
|
80
|
+
const stateStorage = createMockStorage();
|
|
81
|
+
const { requestHook } = await installPlugin(createServerOptions({ storage, stateStorage }));
|
|
82
|
+
|
|
83
|
+
const event = { context: {} } as unknown as H3Event;
|
|
84
|
+
requestHook!(event);
|
|
85
|
+
const sdk = event.context.strivacity.sdk;
|
|
86
|
+
|
|
87
|
+
await expect(sdk.getSession()).resolves.toBeNull();
|
|
88
|
+
|
|
89
|
+
const session = getMockStorage(storage).generateSession(undefined, null);
|
|
90
|
+
await sdk.updateSession(session);
|
|
91
|
+
|
|
92
|
+
await expect(sdk.getSession()).resolves.toEqual(session);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('does not replace an sdk that a previous hook already wired onto the event context', async () => {
|
|
96
|
+
const { requestHook } = await installPlugin(createServerOptions({ storage: createMockStorage(), stateStorage: createMockStorage() }));
|
|
97
|
+
|
|
98
|
+
const existingSdk = { marker: 'existing' };
|
|
99
|
+
const event = { context: { strivacity: { sdk: existingSdk } } } as unknown as H3Event;
|
|
100
|
+
requestHook!(event);
|
|
101
|
+
|
|
102
|
+
expect(event.context.strivacity.sdk).toBe(existingSdk);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('prefers a custom storage/stateStorage/logging/httpClient supplied through the #strivacity-options-* factories over the runtime config', async () => {
|
|
106
|
+
const customStorage = createMockStorage();
|
|
107
|
+
const customStateStorage = createMockStorage();
|
|
108
|
+
const customLogging = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
109
|
+
const customHttpClient = { request: vi.fn(), sendTokenRequest: vi.fn() };
|
|
110
|
+
|
|
111
|
+
vi.doMock('#strivacity-options-storage', () => ({ default: () => customStorage }));
|
|
112
|
+
vi.doMock('#strivacity-options-stateStorage', () => ({ default: () => customStateStorage }));
|
|
113
|
+
vi.doMock('#strivacity-options-logging', () => ({ default: () => customLogging }));
|
|
114
|
+
vi.doMock('#strivacity-options-httpClient', () => ({ default: () => customHttpClient }));
|
|
115
|
+
|
|
116
|
+
const configuredStorage = createMockStorage();
|
|
117
|
+
const { requestHook } = await installPlugin(createServerOptions({ storage: configuredStorage, stateStorage: createMockStorage() }));
|
|
118
|
+
|
|
119
|
+
const event = { context: {} } as unknown as H3Event;
|
|
120
|
+
requestHook!(event);
|
|
121
|
+
const sdk = event.context.strivacity.sdk;
|
|
122
|
+
|
|
123
|
+
const session = getMockStorage(customStorage).generateSession(undefined, null);
|
|
124
|
+
await sdk.updateSession(session);
|
|
125
|
+
|
|
126
|
+
expect(getMockStorage(configuredStorage).keys()).toEqual([]);
|
|
127
|
+
await expect(sdk.getSession()).resolves.toEqual(session);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('falls back to an encrypted cookie storage keyed by the configured secret when no storage is configured', async () => {
|
|
131
|
+
const { requestHook } = await installPlugin(createServerOptions({ storage: undefined, stateStorage: createMockStorage(), secret: 'secret' }));
|
|
132
|
+
|
|
133
|
+
const event = createFakeH3Event();
|
|
134
|
+
|
|
135
|
+
expect(() => requestHook!(event)).not.toThrow();
|
|
136
|
+
await expect(event.context.strivacity.sdk.getSession()).resolves.toBeNull();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import { test, expect, vi } from 'vitest';
|
|
3
|
+
import loginHandler from '../../../src/runtime/server/api/auth/login.get';
|
|
4
|
+
import registerHandler from '../../../src/runtime/server/api/auth/register.get';
|
|
5
|
+
import callbackHandler from '../../../src/runtime/server/api/auth/callback.get';
|
|
6
|
+
import refreshHandler from '../../../src/runtime/server/api/auth/refresh.get';
|
|
7
|
+
import revokeHandler from '../../../src/runtime/server/api/auth/revoke.get';
|
|
8
|
+
import entryHandler from '../../../src/runtime/server/api/auth/entry.get';
|
|
9
|
+
import logoutHandler from '../../../src/runtime/server/api/auth/logout.get';
|
|
10
|
+
import backchannelLogoutHandler from '../../../src/runtime/server/api/auth/backchannel-logout.get';
|
|
11
|
+
|
|
12
|
+
function createFakeEvent(sdk: Record<string, ReturnType<typeof vi.fn>>): H3Event {
|
|
13
|
+
return { context: { strivacity: { sdk } } } as unknown as H3Event;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test.each([
|
|
17
|
+
['login.get', loginHandler, 'handleLogin'],
|
|
18
|
+
['register.get', registerHandler, 'handleRegister'],
|
|
19
|
+
['callback.get', callbackHandler, 'handleCallback'],
|
|
20
|
+
['refresh.get', refreshHandler, 'handleRefresh'],
|
|
21
|
+
['revoke.get', revokeHandler, 'handleRevoke'],
|
|
22
|
+
['entry.get', entryHandler, 'handleEntry'],
|
|
23
|
+
['logout.get', logoutHandler, 'handleLogout'],
|
|
24
|
+
['backchannel-logout.get', backchannelLogoutHandler, 'handleBackChannelLogout'],
|
|
25
|
+
] as const)('%s dispatches to sdk.%s with the event', async (_name, handler, sdkMethod) => {
|
|
26
|
+
const response = new Response(null, { status: 204 });
|
|
27
|
+
const sdk = { [sdkMethod]: vi.fn().mockResolvedValue(response) };
|
|
28
|
+
const event = createFakeEvent(sdk);
|
|
29
|
+
|
|
30
|
+
const result = await handler(event);
|
|
31
|
+
|
|
32
|
+
expect(sdk[sdkMethod]).toHaveBeenCalledWith(event);
|
|
33
|
+
expect(result).toBe(response);
|
|
34
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import { describe, test, expect } from 'vitest';
|
|
3
|
+
import { useStrivacity } from '../../../src/runtime/server/composables/use-strivacity';
|
|
4
|
+
|
|
5
|
+
describe('useStrivacity (server composable)', () => {
|
|
6
|
+
test('returns the sdk instance wired onto the event context by the auth.server nitro plugin', () => {
|
|
7
|
+
const sdk = { getSession: () => Promise.resolve(null) };
|
|
8
|
+
const event = { context: { strivacity: { sdk } } } as unknown as H3Event;
|
|
9
|
+
|
|
10
|
+
expect(useStrivacity(event)).toBe(sdk);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('throws when called without an event', () => {
|
|
14
|
+
expect(() => useStrivacity(undefined as unknown as H3Event)).toThrow('useStrivacity() can not be called without passing an H3Event instance.');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// NOTE: the "throws when called from the client" guard (`if (import.meta.client)`) can't be
|
|
18
|
+
// exercised here - import.meta.client is only ever set to a truthy compile-time constant by
|
|
19
|
+
// Nuxt's own Vite plugin during a real build, which this bare vitest config doesn't run.
|
|
20
|
+
// import.meta.client is falsy in this harness, i.e. it always presents as "server", which is
|
|
21
|
+
// the environment this composable is actually meant to run in.
|
|
22
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { createEncryptedCookieStorage, createSessionIdCookieStorage } from '../../src/runtime/storages/default';
|
|
3
|
+
import { createServerMemoryStorage } from '@strivacity/sdk-core/storages';
|
|
4
|
+
import { flushSetCookies } from '@strivacity/sdk-core/utils';
|
|
5
|
+
import { createFakeH3Event, getResponseCookies } from '../utils/http';
|
|
6
|
+
|
|
7
|
+
function extractCookieHeader(cookies: Array<string>): string {
|
|
8
|
+
return cookies.map((cookie) => cookie.split(';')[0]).join('; ');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('createEncryptedCookieStorage', () => {
|
|
12
|
+
test('returns null for a key with no cookie at all', async () => {
|
|
13
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
14
|
+
const event = createFakeH3Event();
|
|
15
|
+
|
|
16
|
+
await expect(storage.get('sty.session', event)).resolves.toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('returns null/undefined and is a no-op when no event is provided to get/set/delete', async () => {
|
|
20
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
21
|
+
|
|
22
|
+
await expect(storage.get('sty.session', undefined)).resolves.toBeNull();
|
|
23
|
+
await expect(storage.set('sty.session', 'value', undefined)).resolves.toBeUndefined();
|
|
24
|
+
await expect(storage.delete('sty.session', undefined)).resolves.toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('round-trips a small value through a single encrypted cookie', async () => {
|
|
28
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
29
|
+
const outgoing = createFakeH3Event();
|
|
30
|
+
|
|
31
|
+
await storage.set('sty.session', 'small-value', outgoing);
|
|
32
|
+
|
|
33
|
+
const cookies = getResponseCookies(outgoing);
|
|
34
|
+
expect(cookies).toHaveLength(1);
|
|
35
|
+
expect(cookies[0]).toContain('sty.session=');
|
|
36
|
+
|
|
37
|
+
const incoming = createFakeH3Event(extractCookieHeader(cookies));
|
|
38
|
+
|
|
39
|
+
await expect(storage.get('sty.session', incoming)).resolves.toBe('small-value');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('chunks a value larger than the cookie chunk size across multiple numbered cookies and reassembles it on read', async () => {
|
|
43
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
44
|
+
const outgoing = createFakeH3Event();
|
|
45
|
+
const largeValue = 'x'.repeat(10_000);
|
|
46
|
+
|
|
47
|
+
await storage.set('sty.session', largeValue, outgoing);
|
|
48
|
+
|
|
49
|
+
const cookies = getResponseCookies(outgoing);
|
|
50
|
+
expect(cookies.length).toBeGreaterThan(1);
|
|
51
|
+
expect(cookies.some((cookie) => cookie.startsWith('sty.session.0='))).toBe(true);
|
|
52
|
+
// set() always clears the base "sty.session" cookie first (via deleteCookie), so a Max-Age=0
|
|
53
|
+
// expiry for it is expected here; it must not carry an actual value alongside the chunks.
|
|
54
|
+
expect(cookies.some((cookie) => cookie.startsWith('sty.session=') && cookie.includes('Max-Age=0'))).toBe(true);
|
|
55
|
+
|
|
56
|
+
const incoming = createFakeH3Event(extractCookieHeader(cookies));
|
|
57
|
+
|
|
58
|
+
await expect(storage.get('sty.session', incoming)).resolves.toBe(largeValue);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('first clears any previously set chunk cookie(s) for the key before writing a smaller value (avoids leaving stale chunks behind)', async () => {
|
|
62
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
63
|
+
const first = createFakeH3Event();
|
|
64
|
+
|
|
65
|
+
await storage.set('sty.session', 'x'.repeat(10_000), first);
|
|
66
|
+
|
|
67
|
+
// deleteCookie/setCookie only ever see the *incoming request's* cookies, so detecting (and
|
|
68
|
+
// clearing) the previous chunk cookies requires a fresh event carrying them as a real
|
|
69
|
+
// round-trip would - a second set() on the very same (response-only) event never can.
|
|
70
|
+
const second = createFakeH3Event(extractCookieHeader(getResponseCookies(first)));
|
|
71
|
+
await storage.set('sty.session', 'small-value-again', second);
|
|
72
|
+
|
|
73
|
+
const cookies = getResponseCookies(second);
|
|
74
|
+
const chunkCookies = cookies.filter((cookie) => cookie.startsWith('sty.session.'));
|
|
75
|
+
expect(chunkCookies.length).toBeGreaterThan(0);
|
|
76
|
+
expect(chunkCookies.every((cookie) => cookie.includes('Max-Age=0'))).toBe(true);
|
|
77
|
+
|
|
78
|
+
const incoming = createFakeH3Event(extractCookieHeader(cookies));
|
|
79
|
+
await expect(storage.get('sty.session', incoming)).resolves.toBe('small-value-again');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('deletes the unchunked cookie and every numbered chunk cookie present on the incoming request', async () => {
|
|
83
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
84
|
+
const outgoing = createFakeH3Event();
|
|
85
|
+
await storage.set('sty.session', 'x'.repeat(10_000), outgoing);
|
|
86
|
+
|
|
87
|
+
// simulate the browser round-tripping the chunk cookies back on the delete request
|
|
88
|
+
const deleteEvent = createFakeH3Event(extractCookieHeader(getResponseCookies(outgoing)));
|
|
89
|
+
await storage.delete('sty.session', deleteEvent);
|
|
90
|
+
|
|
91
|
+
const expiredCookies = getResponseCookies(deleteEvent);
|
|
92
|
+
expect(expiredCookies.some((cookie) => cookie.startsWith('sty.session=') && cookie.includes('Max-Age=0'))).toBe(true);
|
|
93
|
+
expect(expiredCookies.some((cookie) => cookie.startsWith('sty.session.0=') && cookie.includes('Max-Age=0'))).toBe(true);
|
|
94
|
+
expect(expiredCookies.some((cookie) => cookie.startsWith('sty.session.1=') && cookie.includes('Max-Age=0'))).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('deleting a key with no cookie at all still just expires the base cookie, without throwing', async () => {
|
|
98
|
+
const storage = createEncryptedCookieStorage('secret');
|
|
99
|
+
const event = createFakeH3Event();
|
|
100
|
+
|
|
101
|
+
await expect(storage.delete('sty.session', event)).resolves.toBeUndefined();
|
|
102
|
+
expect(getResponseCookies(event)[0]).toContain('Max-Age=0');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('applies default cookie attributes (httpOnly, secure, lax) and honors overrides', async () => {
|
|
106
|
+
const defaultStorage = createEncryptedCookieStorage('secret');
|
|
107
|
+
const defaultEvent = createFakeH3Event();
|
|
108
|
+
await defaultStorage.set('sty.session', 'value', defaultEvent);
|
|
109
|
+
const [defaultCookie] = getResponseCookies(defaultEvent);
|
|
110
|
+
|
|
111
|
+
expect(defaultCookie).toContain('HttpOnly');
|
|
112
|
+
expect(defaultCookie).toContain('Secure');
|
|
113
|
+
expect(defaultCookie).toContain('SameSite=Lax');
|
|
114
|
+
expect(defaultCookie).toContain('Path=/');
|
|
115
|
+
|
|
116
|
+
const customStorage = createEncryptedCookieStorage('secret', { defaultCookieOptions: { sameSite: 'strict', maxAge: 60 } });
|
|
117
|
+
const customEvent = createFakeH3Event();
|
|
118
|
+
await customStorage.set('sty.session', 'value', customEvent);
|
|
119
|
+
const [customCookie] = getResponseCookies(customEvent);
|
|
120
|
+
|
|
121
|
+
expect(customCookie).toContain('SameSite=Strict');
|
|
122
|
+
expect(customCookie).toContain('Max-Age=60');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('createSessionIdCookieStorage', () => {
|
|
127
|
+
test('adapts the H3Event into a Request for the underlying core session-id cookie storage, round-tripping get/set through it', async () => {
|
|
128
|
+
const underlying = createServerMemoryStorage();
|
|
129
|
+
const storage = createSessionIdCookieStorage(underlying);
|
|
130
|
+
const outgoing = createFakeH3Event();
|
|
131
|
+
|
|
132
|
+
await storage.set('key', 'value', outgoing);
|
|
133
|
+
|
|
134
|
+
// createSessionIdCookieStorage (core) queues its Set-Cookie header in an internal cookie jar
|
|
135
|
+
// keyed by the event object, rather than writing through h3's response headers the way
|
|
136
|
+
// createEncryptedCookieStorage does - flushSetCookies() is how it's meant to be drained.
|
|
137
|
+
const response = flushSetCookies(outgoing, new Response());
|
|
138
|
+
const setCookie = response.headers.get('set-cookie');
|
|
139
|
+
expect(setCookie).toBeTruthy();
|
|
140
|
+
|
|
141
|
+
const incoming = createFakeH3Event(setCookie!.split(';')[0]);
|
|
142
|
+
|
|
143
|
+
await expect(storage.get('key', incoming)).resolves.toBe('value');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('returns null when the incoming event carries no session-id cookie', async () => {
|
|
147
|
+
const storage = createSessionIdCookieStorage(createServerMemoryStorage());
|
|
148
|
+
|
|
149
|
+
await expect(storage.get('key', createFakeH3Event())).resolves.toBeNull();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import * as nuxtStorages from '../../src/runtime/storages/core';
|
|
3
|
+
import * as coreStorages from '@strivacity/sdk-core/storages';
|
|
4
|
+
|
|
5
|
+
test('re-exports the client-safe storages from the core sdk', () => {
|
|
6
|
+
const coreKeys = Object.keys(coreStorages);
|
|
7
|
+
|
|
8
|
+
expect(coreKeys.length).toBeGreaterThan(0);
|
|
9
|
+
|
|
10
|
+
for (const key of Object.keys(nuxtStorages)) {
|
|
11
|
+
expect(coreKeys).toContain(key);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
expect(nuxtStorages).not.toHaveProperty('createSessionIdCookieStorage');
|
|
15
|
+
expect(nuxtStorages).not.toHaveProperty('createEncryptedCookieStorage');
|
|
16
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import * as nuxtUtils from '../../src/runtime/utils/core';
|
|
3
|
+
import * as coreUtils from '@strivacity/sdk-core/utils';
|
|
4
|
+
|
|
5
|
+
test('re-exports utility from the core sdk', () => {
|
|
6
|
+
const coreKeys = Object.keys(coreUtils);
|
|
7
|
+
|
|
8
|
+
expect(coreKeys.length).toBeGreaterThan(0);
|
|
9
|
+
expect(Object.keys(nuxtUtils).sort()).toEqual(coreKeys.sort());
|
|
10
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { App } from 'vue';
|
|
2
|
+
import type { NativeFlow } from '@strivacity/sdk-core/types';
|
|
3
|
+
import type { LoginContext, UseNativeLoginOptions } from '../../src/runtime/types';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
import { createApp } from 'vue';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Points the mocked core `initFlow` at the given flow instance. Must be called before dynamically
|
|
9
|
+
* importing a composable that (transitively) calls `initFlow`, and after `vi.resetModules()` -
|
|
10
|
+
* resetting the module registry recreates the `@strivacity/sdk-core` mock (and its `initFlow`
|
|
11
|
+
* mock function) from scratch, so the previous test's `mockReturnValue` no longer applies.
|
|
12
|
+
*/
|
|
13
|
+
export async function mockInitFlow<T>(flow: T): Promise<void> {
|
|
14
|
+
const core = await import('@strivacity/sdk-core');
|
|
15
|
+
vi.mocked(core.initFlow).mockReturnValue(flow as never);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function mountWithNativeLogin(flow: NativeFlow, options: UseNativeLoginOptions = {}): Promise<{ app: App; login: LoginContext }> {
|
|
19
|
+
await mockInitFlow(flow);
|
|
20
|
+
|
|
21
|
+
const { useNativeLogin } = await import('../../src/runtime/composables/use-native-login');
|
|
22
|
+
let login!: LoginContext;
|
|
23
|
+
const app = createApp({
|
|
24
|
+
setup() {
|
|
25
|
+
login = useNativeLogin(options);
|
|
26
|
+
|
|
27
|
+
return () => null;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
app.mount(document.createElement('div'));
|
|
31
|
+
|
|
32
|
+
return { app, login };
|
|
33
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a minimal fake H3Event backed by a real Node-style req/res pair, since h3's
|
|
6
|
+
* getCookie/setCookie/deleteCookie read and write through `event.node.req`/`event.node.res`
|
|
7
|
+
* rather than through any adapter, and there is no lighter public h3 test helper for this.
|
|
8
|
+
*/
|
|
9
|
+
export function createFakeH3Event(cookieHeader = ''): H3Event {
|
|
10
|
+
const responseHeaders = new Map<string, string | Array<string>>();
|
|
11
|
+
|
|
12
|
+
const res = {
|
|
13
|
+
setHeader: vi.fn((name: string, value: string | Array<string>) => responseHeaders.set(name, value)),
|
|
14
|
+
getHeader: vi.fn((name: string) => responseHeaders.get(name)),
|
|
15
|
+
removeHeader: vi.fn((name: string) => responseHeaders.delete(name)),
|
|
16
|
+
appendHeader: vi.fn((name: string, value: string) => {
|
|
17
|
+
const existing = responseHeaders.get(name);
|
|
18
|
+
|
|
19
|
+
if (existing === undefined) {
|
|
20
|
+
responseHeaders.set(name, value);
|
|
21
|
+
} else {
|
|
22
|
+
responseHeaders.set(name, ([] as Array<string>).concat(existing, value));
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// toWebRequest() (used by createSessionIdCookieStorage) builds a Request from `event.headers` via
|
|
28
|
+
// `new Request(url, { headers })`. Under vitest's happy-dom environment that constructor silently
|
|
29
|
+
// drops the "cookie" header (a forbidden request header per the Fetch spec, unlike real Node's own
|
|
30
|
+
// fetch implementation, which preserves it) - so it's set here via `.set()` on an already-constructed
|
|
31
|
+
// Request instead, then exposed through `event.web.request`, which toWebRequest() returns as-is
|
|
32
|
+
// without reconstructing.
|
|
33
|
+
const webRequest = new Request('http://localhost/');
|
|
34
|
+
if (cookieHeader) {
|
|
35
|
+
webRequest.headers.set('cookie', cookieHeader);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
node: { req: { url: '/', method: 'GET', headers: { cookie: cookieHeader } }, res },
|
|
40
|
+
context: {},
|
|
41
|
+
path: '/',
|
|
42
|
+
method: 'GET',
|
|
43
|
+
headers: webRequest.headers,
|
|
44
|
+
web: { request: webRequest },
|
|
45
|
+
} as unknown as H3Event;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getResponseCookies(event: H3Event): Array<string> {
|
|
49
|
+
const setCookie = (event as unknown as { node: { res: { getHeader: (name: string) => string | Array<string> | undefined } } }).node.res.getHeader(
|
|
50
|
+
'set-cookie',
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!setCookie) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Array.isArray(setCookie) ? setCookie : [setCookie];
|
|
58
|
+
}
|
package/tsconfig.json
ADDED
package/vite.config.mts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { defineConfig } from 'vitest/config';
|
|
3
|
+
import { getVitestConfig } from '@strivacity/testing/vitest/config';
|
|
4
|
+
|
|
5
|
+
// Nuxt's virtual/auto-import modules (#app, #imports, #strivacity-options-*) only exist inside a
|
|
6
|
+
// running Nuxt dev/build context. Point them at local stubs so plain Vite/vitest can resolve the
|
|
7
|
+
// bare specifiers at transform time; individual tests can still vi.mock() over these stubs.
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
test: getVitestConfig('nuxt'),
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
'#app': fileURLToPath(new URL('./testing/stubs/app.ts', import.meta.url)),
|
|
13
|
+
'#imports': fileURLToPath(new URL('./testing/stubs/imports.ts', import.meta.url)),
|
|
14
|
+
'#strivacity-options-logging': fileURLToPath(new URL('./testing/stubs/options-logging.ts', import.meta.url)),
|
|
15
|
+
'#strivacity-options-httpClient': fileURLToPath(new URL('./testing/stubs/options-httpClient.ts', import.meta.url)),
|
|
16
|
+
'#strivacity-options-storage': fileURLToPath(new URL('./testing/stubs/options-storage.ts', import.meta.url)),
|
|
17
|
+
'#strivacity-options-stateStorage': fileURLToPath(new URL('./testing/stubs/options-stateStorage.ts', import.meta.url)),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import type { SDKLogging } from '@strivacity/sdk-core';
|
|
3
|
-
|
|
4
|
-
declare class CustomLogging implements SDKLogging {
|
|
5
|
-
xEventId: string | undefined;
|
|
6
|
-
|
|
7
|
-
debug(message: string): void;
|
|
8
|
-
info(message: string): void;
|
|
9
|
-
warn(message: string): void;
|
|
10
|
-
error(message: string, error: Error): void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default CustomLogging
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default undefined
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import type { SDKStorage } from '@strivacity/sdk-core';
|
|
3
|
-
|
|
4
|
-
declare class CustomStorage implements SDKStorage {
|
|
5
|
-
get(key: string): Promise<string | null>;
|
|
6
|
-
delete(key: string): Promise<void>;
|
|
7
|
-
set(key: string, value: string): Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default CustomStorage
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default class l{async get(e){return await Promise.resolve(globalThis?.window?.localStorage?.getItem(e))}async delete(e){await Promise.resolve(globalThis?.window?.localStorage.removeItem(e))}async set(e,o){await Promise.resolve(globalThis?.window?.localStorage.setItem(e,o))}}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { PopupContext, RedirectContext, NativeContext } from '../types.js';
|
|
2
|
-
export { createCredential, getCredential } from '@strivacity/sdk-core/utils/credentials';
|
|
3
|
-
/**
|
|
4
|
-
* Retrieves the Strivacity SDK context for Popup or Redirect flows.
|
|
5
|
-
*
|
|
6
|
-
* @template T The type of context, either PopupContext or RedirectContext.
|
|
7
|
-
*
|
|
8
|
-
* @throws {Error} If the Strivacity SDK context is not found.
|
|
9
|
-
*
|
|
10
|
-
* @returns {T} The Strivacity SDK context, typed as either PopupContext or RedirectContext or NativeContext.
|
|
11
|
-
*/
|
|
12
|
-
export declare const useStrivacity: <T extends PopupContext | RedirectContext | NativeContext>() => T;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import StorageClass from "#build/strivacity-sdk-storage";
|
|
2
|
-
import LoggingClass from "#build/strivacity-sdk-logging";
|
|
3
|
-
import { ref, useRuntimeConfig } from "#imports";
|
|
4
|
-
import { initFlow } from "@strivacity/sdk-core";
|
|
5
|
-
export { createCredential, getCredential } from "@strivacity/sdk-core/utils/credentials";
|
|
6
|
-
const loadingRef = ref(true);
|
|
7
|
-
const optionsRef = ref({});
|
|
8
|
-
const isAuthenticatedRef = ref(false);
|
|
9
|
-
const idTokenClaimsRef = ref(null);
|
|
10
|
-
const accessTokenRef = ref(null);
|
|
11
|
-
const refreshTokenRef = ref(null);
|
|
12
|
-
const accessTokenExpiredRef = ref(true);
|
|
13
|
-
const accessTokenExpirationDateRef = ref(null);
|
|
14
|
-
let sdk;
|
|
15
|
-
const updateSession = async () => {
|
|
16
|
-
isAuthenticatedRef.value = await sdk.isAuthenticated;
|
|
17
|
-
idTokenClaimsRef.value = sdk.idTokenClaims || null;
|
|
18
|
-
accessTokenRef.value = sdk.accessToken || null;
|
|
19
|
-
refreshTokenRef.value = sdk.refreshToken || null;
|
|
20
|
-
accessTokenExpiredRef.value = sdk.accessTokenExpired;
|
|
21
|
-
accessTokenExpirationDateRef.value = sdk.accessTokenExpirationDate || null;
|
|
22
|
-
if (loadingRef.value) {
|
|
23
|
-
loadingRef.value = false;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
export const useStrivacity = () => {
|
|
27
|
-
if (!sdk) {
|
|
28
|
-
sdk = initFlow({ ...useRuntimeConfig().public.strivacity, storage: StorageClass, logging: LoggingClass });
|
|
29
|
-
optionsRef.value = sdk.options;
|
|
30
|
-
sdk.subscribeToEvent("init", updateSession);
|
|
31
|
-
sdk.subscribeToEvent("loggedIn", updateSession);
|
|
32
|
-
sdk.subscribeToEvent("sessionLoaded", updateSession);
|
|
33
|
-
sdk.subscribeToEvent("tokenRefreshed", updateSession);
|
|
34
|
-
sdk.subscribeToEvent("tokenRefreshFailed", updateSession);
|
|
35
|
-
sdk.subscribeToEvent("logoutInitiated", updateSession);
|
|
36
|
-
sdk.subscribeToEvent("tokenRevoked", updateSession);
|
|
37
|
-
sdk.subscribeToEvent("tokenRevokeFailed", updateSession);
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
sdk,
|
|
41
|
-
loading: loadingRef,
|
|
42
|
-
options: optionsRef,
|
|
43
|
-
isAuthenticated: isAuthenticatedRef,
|
|
44
|
-
idTokenClaims: idTokenClaimsRef,
|
|
45
|
-
accessToken: accessTokenRef,
|
|
46
|
-
refreshToken: refreshTokenRef,
|
|
47
|
-
accessTokenExpired: accessTokenExpiredRef,
|
|
48
|
-
accessTokenExpirationDate: accessTokenExpirationDateRef,
|
|
49
|
-
login: async (options) => {
|
|
50
|
-
if (sdk.options.mode === "native") {
|
|
51
|
-
return sdk.login(options);
|
|
52
|
-
}
|
|
53
|
-
await sdk.login(options);
|
|
54
|
-
await updateSession();
|
|
55
|
-
},
|
|
56
|
-
register: async (options) => {
|
|
57
|
-
if (sdk.options.mode === "native") {
|
|
58
|
-
return sdk.register(options);
|
|
59
|
-
}
|
|
60
|
-
await sdk.register(options);
|
|
61
|
-
await updateSession();
|
|
62
|
-
},
|
|
63
|
-
refresh: async () => {
|
|
64
|
-
await sdk.refresh();
|
|
65
|
-
await updateSession();
|
|
66
|
-
},
|
|
67
|
-
entry: async (url) => {
|
|
68
|
-
return await sdk.entry(url);
|
|
69
|
-
},
|
|
70
|
-
revoke: async () => {
|
|
71
|
-
await sdk.revoke();
|
|
72
|
-
await updateSession();
|
|
73
|
-
},
|
|
74
|
-
logout: async (options) => {
|
|
75
|
-
await sdk.logout(options);
|
|
76
|
-
await updateSession();
|
|
77
|
-
},
|
|
78
|
-
handleCallback: async (url) => {
|
|
79
|
-
await sdk.handleCallback(url);
|
|
80
|
-
await updateSession();
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
};
|