@htlkg/core 0.0.2 → 0.0.3
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 +51 -0
- package/dist/index.d.ts +219 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -8
- package/src/amplify-astro-adapter/amplify-astro-adapter.md +167 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.test.ts +296 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.ts +97 -0
- package/src/amplify-astro-adapter/createRunWithAmplifyServerContext.ts +84 -0
- package/src/amplify-astro-adapter/errors.test.ts +115 -0
- package/src/amplify-astro-adapter/errors.ts +105 -0
- package/src/amplify-astro-adapter/globalSettings.test.ts +78 -0
- package/src/amplify-astro-adapter/globalSettings.ts +16 -0
- package/src/amplify-astro-adapter/index.ts +14 -0
- package/src/amplify-astro-adapter/types.ts +55 -0
- package/src/auth/auth.md +178 -0
- package/src/auth/index.test.ts +180 -0
- package/src/auth/index.ts +294 -0
- package/src/constants/constants.md +132 -0
- package/src/constants/index.test.ts +116 -0
- package/src/constants/index.ts +98 -0
- package/src/core-exports.property.test.ts +186 -0
- package/src/errors/errors.md +177 -0
- package/src/errors/index.test.ts +153 -0
- package/src/errors/index.ts +134 -0
- package/src/index.ts +65 -0
- package/src/routes/index.ts +225 -0
- package/src/routes/routes.md +189 -0
- package/src/types/index.ts +94 -0
- package/src/types/types.md +144 -0
- package/src/utils/index.test.ts +257 -0
- package/src/utils/index.ts +112 -0
- package/src/utils/logger.ts +88 -0
- package/src/utils/utils.md +199 -0
- package/src/workspace.property.test.ts +235 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
AmplifyAstroAdapterError,
|
|
4
|
+
ErrorCodes,
|
|
5
|
+
createConfigMissingError,
|
|
6
|
+
createAuthFailedError,
|
|
7
|
+
createCookieError,
|
|
8
|
+
createGraphQLError,
|
|
9
|
+
createContextCreationError,
|
|
10
|
+
createTokenRefreshError,
|
|
11
|
+
} from "./errors";
|
|
12
|
+
|
|
13
|
+
describe("AmplifyAstroAdapterError", () => {
|
|
14
|
+
it("should create error with message and code", () => {
|
|
15
|
+
const error = new AmplifyAstroAdapterError(
|
|
16
|
+
"Test error",
|
|
17
|
+
"TEST_CODE"
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(error.message).toBe("Test error");
|
|
21
|
+
expect(error.code).toBe("TEST_CODE");
|
|
22
|
+
expect(error.name).toBe("AmplifyAstroAdapterError");
|
|
23
|
+
expect(error.recoverySuggestion).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should create error with recovery suggestion", () => {
|
|
27
|
+
const error = new AmplifyAstroAdapterError(
|
|
28
|
+
"Test error",
|
|
29
|
+
"TEST_CODE",
|
|
30
|
+
"Try this fix"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(error.recoverySuggestion).toBe("Try this fix");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should be instanceof Error", () => {
|
|
37
|
+
const error = new AmplifyAstroAdapterError("Test", "CODE");
|
|
38
|
+
expect(error).toBeInstanceOf(Error);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("Error factory functions", () => {
|
|
43
|
+
describe("createConfigMissingError", () => {
|
|
44
|
+
it("should create config missing error", () => {
|
|
45
|
+
const error = createConfigMissingError("userPoolId");
|
|
46
|
+
|
|
47
|
+
expect(error.message).toContain("userPoolId");
|
|
48
|
+
expect(error.code).toBe(ErrorCodes.CONFIG_MISSING);
|
|
49
|
+
expect(error.recoverySuggestion).toContain("amplify_outputs.json");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("createAuthFailedError", () => {
|
|
54
|
+
it("should create auth failed error", () => {
|
|
55
|
+
const error = createAuthFailedError("Invalid token");
|
|
56
|
+
|
|
57
|
+
expect(error.message).toContain("Invalid token");
|
|
58
|
+
expect(error.code).toBe(ErrorCodes.AUTH_FAILED);
|
|
59
|
+
expect(error.recoverySuggestion).toContain("tokens are valid");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("createCookieError", () => {
|
|
64
|
+
it("should create cookie error", () => {
|
|
65
|
+
const error = createCookieError("set", "Invalid format");
|
|
66
|
+
|
|
67
|
+
expect(error.message).toContain("set");
|
|
68
|
+
expect(error.message).toContain("Invalid format");
|
|
69
|
+
expect(error.code).toBe(ErrorCodes.COOKIE_ERROR);
|
|
70
|
+
expect(error.recoverySuggestion).toContain("cookie");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("createGraphQLError", () => {
|
|
75
|
+
it("should create GraphQL error", () => {
|
|
76
|
+
const error = createGraphQLError("query", "Network error");
|
|
77
|
+
|
|
78
|
+
expect(error.message).toContain("query");
|
|
79
|
+
expect(error.message).toContain("Network error");
|
|
80
|
+
expect(error.code).toBe(ErrorCodes.GRAPHQL_ERROR);
|
|
81
|
+
expect(error.recoverySuggestion).toContain("network");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("createContextCreationError", () => {
|
|
86
|
+
it("should create context creation error", () => {
|
|
87
|
+
const error = createContextCreationError("Missing config");
|
|
88
|
+
|
|
89
|
+
expect(error.message).toContain("Missing config");
|
|
90
|
+
expect(error.code).toBe(ErrorCodes.CONTEXT_CREATION_FAILED);
|
|
91
|
+
expect(error.recoverySuggestion).toContain("Amplify configuration");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("createTokenRefreshError", () => {
|
|
96
|
+
it("should create token refresh error", () => {
|
|
97
|
+
const error = createTokenRefreshError("Expired refresh token");
|
|
98
|
+
|
|
99
|
+
expect(error.message).toContain("Expired refresh token");
|
|
100
|
+
expect(error.code).toBe(ErrorCodes.TOKEN_REFRESH_FAILED);
|
|
101
|
+
expect(error.recoverySuggestion).toContain("re-authenticate");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("ErrorCodes", () => {
|
|
107
|
+
it("should have all expected error codes", () => {
|
|
108
|
+
expect(ErrorCodes.CONFIG_MISSING).toBe("CONFIG_MISSING");
|
|
109
|
+
expect(ErrorCodes.AUTH_FAILED).toBe("AUTH_FAILED");
|
|
110
|
+
expect(ErrorCodes.COOKIE_ERROR).toBe("COOKIE_ERROR");
|
|
111
|
+
expect(ErrorCodes.GRAPHQL_ERROR).toBe("GRAPHQL_ERROR");
|
|
112
|
+
expect(ErrorCodes.CONTEXT_CREATION_FAILED).toBe("CONTEXT_CREATION_FAILED");
|
|
113
|
+
expect(ErrorCodes.TOKEN_REFRESH_FAILED).toBe("TOKEN_REFRESH_FAILED");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classes for adapter-specific errors
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base error class for Amplify Astro Adapter errors
|
|
7
|
+
*/
|
|
8
|
+
export class AmplifyAstroAdapterError extends Error {
|
|
9
|
+
public readonly code: string;
|
|
10
|
+
public readonly recoverySuggestion?: string;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
message: string,
|
|
14
|
+
code: string,
|
|
15
|
+
recoverySuggestion?: string
|
|
16
|
+
) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'AmplifyAstroAdapterError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.recoverySuggestion = recoverySuggestion;
|
|
21
|
+
|
|
22
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
23
|
+
if (Error.captureStackTrace) {
|
|
24
|
+
Error.captureStackTrace(this, AmplifyAstroAdapterError);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Error codes for different types of adapter errors
|
|
31
|
+
*/
|
|
32
|
+
export const ErrorCodes = {
|
|
33
|
+
CONFIG_MISSING: 'CONFIG_MISSING',
|
|
34
|
+
AUTH_FAILED: 'AUTH_FAILED',
|
|
35
|
+
COOKIE_ERROR: 'COOKIE_ERROR',
|
|
36
|
+
GRAPHQL_ERROR: 'GRAPHQL_ERROR',
|
|
37
|
+
CONTEXT_CREATION_FAILED: 'CONTEXT_CREATION_FAILED',
|
|
38
|
+
TOKEN_REFRESH_FAILED: 'TOKEN_REFRESH_FAILED',
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a configuration missing error
|
|
43
|
+
*/
|
|
44
|
+
export function createConfigMissingError(missingConfig: string): AmplifyAstroAdapterError {
|
|
45
|
+
return new AmplifyAstroAdapterError(
|
|
46
|
+
`Amplify configuration is missing: ${missingConfig}`,
|
|
47
|
+
ErrorCodes.CONFIG_MISSING,
|
|
48
|
+
'Ensure amplify_outputs.json is properly configured and accessible to the adapter'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates an authentication failed error
|
|
54
|
+
*/
|
|
55
|
+
export function createAuthFailedError(reason: string): AmplifyAstroAdapterError {
|
|
56
|
+
return new AmplifyAstroAdapterError(
|
|
57
|
+
`Authentication failed: ${reason}`,
|
|
58
|
+
ErrorCodes.AUTH_FAILED,
|
|
59
|
+
'Check that authentication tokens are valid and not expired'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a cookie error
|
|
65
|
+
*/
|
|
66
|
+
export function createCookieError(operation: string, reason: string): AmplifyAstroAdapterError {
|
|
67
|
+
return new AmplifyAstroAdapterError(
|
|
68
|
+
`Cookie ${operation} failed: ${reason}`,
|
|
69
|
+
ErrorCodes.COOKIE_ERROR,
|
|
70
|
+
'Verify cookie data format and ensure cookies are properly set in the response'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a GraphQL error
|
|
76
|
+
*/
|
|
77
|
+
export function createGraphQLError(operation: string, reason: string): AmplifyAstroAdapterError {
|
|
78
|
+
return new AmplifyAstroAdapterError(
|
|
79
|
+
`GraphQL ${operation} failed: ${reason}`,
|
|
80
|
+
ErrorCodes.GRAPHQL_ERROR,
|
|
81
|
+
'Check network connectivity and GraphQL schema configuration'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a context creation failed error
|
|
87
|
+
*/
|
|
88
|
+
export function createContextCreationError(reason: string): AmplifyAstroAdapterError {
|
|
89
|
+
return new AmplifyAstroAdapterError(
|
|
90
|
+
`Server context creation failed: ${reason}`,
|
|
91
|
+
ErrorCodes.CONTEXT_CREATION_FAILED,
|
|
92
|
+
'Verify Amplify configuration and authentication setup'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates a token refresh failed error
|
|
98
|
+
*/
|
|
99
|
+
export function createTokenRefreshError(reason: string): AmplifyAstroAdapterError {
|
|
100
|
+
return new AmplifyAstroAdapterError(
|
|
101
|
+
`Token refresh failed: ${reason}`,
|
|
102
|
+
ErrorCodes.TOKEN_REFRESH_FAILED,
|
|
103
|
+
'User may need to re-authenticate or check refresh token validity'
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { globalSettings } from "./globalSettings";
|
|
3
|
+
|
|
4
|
+
describe("globalSettings", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// Reset settings before each test
|
|
7
|
+
globalSettings.setRuntimeOptions({});
|
|
8
|
+
globalSettings.setIsSSLOrigin(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("runtimeOptions", () => {
|
|
12
|
+
it("should set and get runtime options", () => {
|
|
13
|
+
const options = {
|
|
14
|
+
cookies: {
|
|
15
|
+
domain: "example.com",
|
|
16
|
+
httpOnly: true,
|
|
17
|
+
secure: true,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
globalSettings.setRuntimeOptions(options);
|
|
22
|
+
expect(globalSettings.getRuntimeOptions()).toEqual(options);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should handle empty options", () => {
|
|
26
|
+
globalSettings.setRuntimeOptions({});
|
|
27
|
+
expect(globalSettings.getRuntimeOptions()).toEqual({});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should handle undefined options", () => {
|
|
31
|
+
globalSettings.setRuntimeOptions(undefined as any);
|
|
32
|
+
expect(globalSettings.getRuntimeOptions()).toEqual({});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("serverSideAuth", () => {
|
|
37
|
+
it("should be enabled by default", () => {
|
|
38
|
+
// The implementation starts with serverSideAuthEnabled = true
|
|
39
|
+
expect(globalSettings.isServerSideAuthEnabled()).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should enable server-side auth", () => {
|
|
43
|
+
globalSettings.enableServerSideAuth();
|
|
44
|
+
expect(globalSettings.isServerSideAuthEnabled()).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should remain enabled after multiple calls", () => {
|
|
48
|
+
globalSettings.enableServerSideAuth();
|
|
49
|
+
globalSettings.enableServerSideAuth();
|
|
50
|
+
expect(globalSettings.isServerSideAuthEnabled()).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("SSL origin", () => {
|
|
55
|
+
it("should start as false", () => {
|
|
56
|
+
expect(globalSettings.isSSLOrigin()).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should set SSL origin to true", () => {
|
|
60
|
+
globalSettings.setIsSSLOrigin(true);
|
|
61
|
+
expect(globalSettings.isSSLOrigin()).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should set SSL origin to false", () => {
|
|
65
|
+
globalSettings.setIsSSLOrigin(true);
|
|
66
|
+
globalSettings.setIsSSLOrigin(false);
|
|
67
|
+
expect(globalSettings.isSSLOrigin()).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should toggle SSL origin", () => {
|
|
71
|
+
expect(globalSettings.isSSLOrigin()).toBe(false);
|
|
72
|
+
globalSettings.setIsSSLOrigin(true);
|
|
73
|
+
expect(globalSettings.isSSLOrigin()).toBe(true);
|
|
74
|
+
globalSettings.setIsSSLOrigin(false);
|
|
75
|
+
expect(globalSettings.isSSLOrigin()).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AstroServer } from './types';
|
|
2
|
+
|
|
3
|
+
export const globalSettings: AstroServer.GlobalSettings = (() => {
|
|
4
|
+
let runtimeOptions: AstroServer.RuntimeOptions = {};
|
|
5
|
+
let serverSideAuthEnabled = true;
|
|
6
|
+
let sslOrigin = false;
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
setRuntimeOptions(opts) { runtimeOptions = opts ?? {}; },
|
|
10
|
+
getRuntimeOptions() { return runtimeOptions; },
|
|
11
|
+
enableServerSideAuth() { serverSideAuthEnabled = true; },
|
|
12
|
+
isServerSideAuthEnabled() { return serverSideAuthEnabled; },
|
|
13
|
+
setIsSSLOrigin(v: boolean) { sslOrigin = v; },
|
|
14
|
+
isSSLOrigin() { return sslOrigin; },
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amplify Astro Adapter
|
|
3
|
+
*
|
|
4
|
+
* Provides server-side authentication support for Astro with AWS Amplify
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { createRunWithAmplifyServerContext } from './createRunWithAmplifyServerContext';
|
|
8
|
+
export { createCookieStorageAdapterFromAstroContext } from './createCookieStorageAdapterFromAstroContext';
|
|
9
|
+
export { globalSettings } from './globalSettings';
|
|
10
|
+
export * from './types';
|
|
11
|
+
export * from './errors';
|
|
12
|
+
|
|
13
|
+
// Re-export logger for convenience
|
|
14
|
+
export { logger, createLogger } from '../utils/logger';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ResourcesConfig } from 'aws-amplify';
|
|
2
|
+
import type { CookieStorage } from 'aws-amplify/adapter-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Amplify configuration structure matching amplify_outputs.json format
|
|
6
|
+
*/
|
|
7
|
+
export type AstroAmplifyConfig = ResourcesConfig;
|
|
8
|
+
|
|
9
|
+
export namespace AstroServer {
|
|
10
|
+
export type RuntimeCookieOptions = CookieStorage.SetCookieOptions & {
|
|
11
|
+
domain?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface RuntimeOptions {
|
|
15
|
+
cookies?: RuntimeCookieOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AstroComponentContext {
|
|
19
|
+
cookies: import('astro').AstroGlobal['cookies'];
|
|
20
|
+
request: Request;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface APIEndpointContext {
|
|
24
|
+
cookies: import('astro').APIContext['cookies'];
|
|
25
|
+
request: Request;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ServerContext =
|
|
29
|
+
| AstroComponentContext
|
|
30
|
+
| APIEndpointContext
|
|
31
|
+
| null;
|
|
32
|
+
|
|
33
|
+
export interface GlobalSettings {
|
|
34
|
+
setRuntimeOptions(opts: RuntimeOptions): void;
|
|
35
|
+
getRuntimeOptions(): RuntimeOptions;
|
|
36
|
+
enableServerSideAuth(): void;
|
|
37
|
+
isServerSideAuthEnabled(): boolean;
|
|
38
|
+
setIsSSLOrigin(val: boolean): void;
|
|
39
|
+
isSSLOrigin(): boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type RunOperationWithContext = <T>(input: {
|
|
43
|
+
astroServerContext: ServerContext;
|
|
44
|
+
operation: (contextSpec: unknown) => Promise<T>;
|
|
45
|
+
}) => Promise<T>;
|
|
46
|
+
|
|
47
|
+
export interface CreateServerRunnerInput {
|
|
48
|
+
config: ResourcesConfig;
|
|
49
|
+
runtimeOptions?: RuntimeOptions;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CreateServerRunnerOutput {
|
|
53
|
+
runWithAmplifyServerContext: RunOperationWithContext;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/auth/auth.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Auth Module
|
|
2
|
+
|
|
3
|
+
Core authentication functions for server-side and client-side user management with AWS Cognito.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
getUser,
|
|
10
|
+
getClientUser,
|
|
11
|
+
hasAccessToBrand,
|
|
12
|
+
hasAccessToAccount,
|
|
13
|
+
isAdminUser,
|
|
14
|
+
isSuperAdminUser,
|
|
15
|
+
} from '@htlkg/core/auth';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## User Interface
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
interface User {
|
|
22
|
+
username: string;
|
|
23
|
+
email: string;
|
|
24
|
+
brandIds: number[];
|
|
25
|
+
accountIds: number[];
|
|
26
|
+
isAdmin: boolean;
|
|
27
|
+
isSuperAdmin: boolean;
|
|
28
|
+
roles: string[];
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Server-Side Authentication
|
|
33
|
+
|
|
34
|
+
### getUser
|
|
35
|
+
|
|
36
|
+
Retrieves the authenticated user from Astro's API context using Cognito cookies.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import type { APIContext } from 'astro';
|
|
40
|
+
import { getUser } from '@htlkg/core/auth';
|
|
41
|
+
|
|
42
|
+
export async function GET(context: APIContext) {
|
|
43
|
+
const user = await getUser(context);
|
|
44
|
+
|
|
45
|
+
if (!user) {
|
|
46
|
+
return new Response('Unauthorized', { status: 401 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new Response(JSON.stringify(user));
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In Astro pages:
|
|
54
|
+
|
|
55
|
+
```astro
|
|
56
|
+
---
|
|
57
|
+
import { getUser } from '@htlkg/core/auth';
|
|
58
|
+
|
|
59
|
+
const user = await getUser(Astro);
|
|
60
|
+
|
|
61
|
+
if (!user) {
|
|
62
|
+
return Astro.redirect('/login');
|
|
63
|
+
}
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
<h1>Welcome, {user.username}</h1>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Client-Side Authentication
|
|
70
|
+
|
|
71
|
+
### getClientUser
|
|
72
|
+
|
|
73
|
+
Retrieves the authenticated user on the client side.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { getClientUser } from '@htlkg/core/auth';
|
|
77
|
+
|
|
78
|
+
const user = await getClientUser();
|
|
79
|
+
|
|
80
|
+
if (user) {
|
|
81
|
+
console.log(`Logged in as ${user.email}`);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Access Control Helpers
|
|
86
|
+
|
|
87
|
+
### hasAccessToBrand
|
|
88
|
+
|
|
89
|
+
Check if user has access to a specific brand.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { getUser, hasAccessToBrand } from '@htlkg/core/auth';
|
|
93
|
+
|
|
94
|
+
const user = await getUser(context);
|
|
95
|
+
const brandId = 123;
|
|
96
|
+
|
|
97
|
+
if (!hasAccessToBrand(user, brandId)) {
|
|
98
|
+
return new Response('Forbidden', { status: 403 });
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### hasAccessToAccount
|
|
103
|
+
|
|
104
|
+
Check if user has access to a specific account.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { getUser, hasAccessToAccount } from '@htlkg/core/auth';
|
|
108
|
+
|
|
109
|
+
const user = await getUser(context);
|
|
110
|
+
|
|
111
|
+
if (hasAccessToAccount(user, accountId)) {
|
|
112
|
+
// User can access this account
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### isAdminUser / isSuperAdminUser
|
|
117
|
+
|
|
118
|
+
Check admin status.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { getUser, isAdminUser, isSuperAdminUser } from '@htlkg/core/auth';
|
|
122
|
+
|
|
123
|
+
const user = await getUser(context);
|
|
124
|
+
|
|
125
|
+
if (isAdminUser(user)) {
|
|
126
|
+
// User is admin or super admin
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (isSuperAdminUser(user)) {
|
|
130
|
+
// User is super admin only
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Cognito Groups
|
|
135
|
+
|
|
136
|
+
The module recognizes these Cognito groups:
|
|
137
|
+
|
|
138
|
+
| Group | Admin | Super Admin |
|
|
139
|
+
|-------|-------|-------------|
|
|
140
|
+
| `admin` | ✓ | ✗ |
|
|
141
|
+
| `ADMINS` | ✓ | ✗ |
|
|
142
|
+
| `SUPER_ADMINS` | ✓ | ✓ |
|
|
143
|
+
|
|
144
|
+
## Custom Attributes
|
|
145
|
+
|
|
146
|
+
User attributes are parsed from Cognito tokens:
|
|
147
|
+
|
|
148
|
+
- `custom:brand_ids` - Comma-separated brand IDs
|
|
149
|
+
- `custom:account_ids` - Comma-separated account IDs
|
|
150
|
+
- `email` - From ID token
|
|
151
|
+
- `cognito:groups` - User roles/groups
|
|
152
|
+
|
|
153
|
+
## Error Handling
|
|
154
|
+
|
|
155
|
+
Authentication errors are logged without exposing sensitive data:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const user = await getUser(context);
|
|
159
|
+
// Returns null on any auth error
|
|
160
|
+
// Errors logged with sanitized messages (tokens masked)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Middleware Placeholders
|
|
164
|
+
|
|
165
|
+
These functions are placeholders that should be imported from `@htlkg/astro/middleware`:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// These throw errors if called directly from @htlkg/core
|
|
169
|
+
requireAuth(context, loginUrl);
|
|
170
|
+
requireAdminAccess(context, loginUrl);
|
|
171
|
+
requireBrandAccess(context, brandId, loginUrl);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Import from the correct package:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { requireAuth } from '@htlkg/astro/middleware';
|
|
178
|
+
```
|