@uva-fnwi/datanose-core 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 ADDED
@@ -0,0 +1,151 @@
1
+ # @uva-fnwi/datanose-core
2
+
3
+ Shared authentication library for DataNose applications. Provides an OIDC-based auth flow (via `oidc-client-ts`) with support for both SURFconext and Canvas LTI providers.
4
+
5
+ ## What it provides
6
+
7
+ - **`AuthProvider`** — React context provider that initialises authentication and exposes auth methods via context.
8
+ - **`useAuth`** — Hook to access the auth context from any child component.
9
+ - **`AuthService`** — Lower-level class for apps that manage auth state themselves (e.g. with Redux).
10
+ - **`authReducer`** — Redux-compatible reducer for auth state.
11
+ - **Selectors** — `selectAuthUser`, `selectAuthStatus`, `selectAuthProviderType`, `selectIsAuthenticated`.
12
+ - **Helpers** — `isEmbedded`, `isEmbeddedInCanvas`, `isImpersonating`, `getDataFromToken`, `getUserAndMetadataForCanvasToken`, `getCanvasTokenFromLocalStorage`, `setCanvasTokenInLocalStorage`, `convertUserToObject`, `isUserAuthenticated`.
13
+ - **Types** — `AuthConfig`, `AuthState`, `AuthEventCallbacks`, `ProviderType`, `CustomUserState`.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @uva-fnwi/datanose-core
19
+ # or
20
+ pnpm add @uva-fnwi/datanose-core
21
+ ```
22
+
23
+ ### As a pnpm workspace dependency (monorepo)
24
+
25
+ ```json
26
+ "dependencies": {
27
+ "@uva-fnwi/datanose-core": "workspace:*"
28
+ }
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Option 1: `AuthProvider` + `useAuth` (recommended for React apps)
34
+
35
+ Wrap your app with `AuthProvider` and access auth state with `useAuth` in any child:
36
+
37
+ ```tsx
38
+ import {AuthProvider, useAuth} from "@uva-fnwi/datanose-core";
39
+
40
+ const config = {
41
+ authority: import.meta.env.VITE_AUTH_AUTHORITY,
42
+ clientId: import.meta.env.VITE_AUTH_CLIENT_ID,
43
+ redirectUri: window.location.origin,
44
+ logoutUri: import.meta.env.VITE_AUTH_LOGOUT_URL,
45
+ };
46
+
47
+ function App() {
48
+ return (
49
+ <AuthProvider config={config} events={{}}>
50
+ <Main />
51
+ </AuthProvider>
52
+ );
53
+ }
54
+
55
+ function Main() {
56
+ const {isAuthenticated, user, surfStartLogin, surfLogout} = useAuth();
57
+
58
+ if (!isAuthenticated) {
59
+ return <button onClick={() => surfStartLogin(undefined)}>Log in</button>;
60
+ }
61
+
62
+ return (
63
+ <div>
64
+ <p>Hello, {user?.profile.name}</p>
65
+ <button onClick={surfLogout}>Log out</button>
66
+ </div>
67
+ );
68
+ }
69
+ ```
70
+
71
+ ### Option 2: `AuthService` + `authReducer` (for Redux-based apps)
72
+
73
+ Use this when you want auth state in your Redux store:
74
+
75
+ ```ts
76
+ import {authReducer, AuthService, selectIsAuthenticated} from "@uva-fnwi/datanose-core";
77
+ import {configureStore} from "@reduxjs/toolkit";
78
+
79
+ export const store = configureStore({
80
+ reducer: {
81
+ auth: authReducer,
82
+ // ... other reducers
83
+ },
84
+ });
85
+
86
+ export const authClient = new AuthService({
87
+ authority: import.meta.env.VITE_AUTH_AUTHORITY,
88
+ clientId: import.meta.env.VITE_AUTH_CLIENT_ID,
89
+ redirectUri: window.location.origin,
90
+ logoutUri: import.meta.env.VITE_AUTH_LOGOUT_URL,
91
+ });
92
+
93
+ // Initialise on app start
94
+ await authClient.initialize((action) => store.dispatch(action), {
95
+ onUserLoaded: (user) => console.log("User loaded", user),
96
+ });
97
+
98
+ // Read state via selectors
99
+ const isAuthenticated = selectIsAuthenticated(store.getState().auth);
100
+ ```
101
+
102
+ ## API reference
103
+
104
+ ### `AuthProvider`
105
+
106
+ | Prop | Type | Description |
107
+ | ---------- | -------------------- | ----------------------------------------------------------------- |
108
+ | `config` | `AuthConfig` | OIDC configuration (authority, clientId, redirectUri, logoutUri?) |
109
+ | `events` | `AuthEventCallbacks` | Optional OIDC event callbacks |
110
+ | `children` | `ReactNode` | — |
111
+
112
+ ### `AuthConfig`
113
+
114
+ ```ts
115
+ type AuthConfig = {
116
+ authority: string;
117
+ clientId: string;
118
+ redirectUri: string;
119
+ logoutUri?: string;
120
+ };
121
+ ```
122
+
123
+ ### `useAuth`
124
+
125
+ Returns `AuthContextProps` (extends `AuthState` with all auth methods). Throws if called outside `<AuthProvider>`.
126
+
127
+ ### Selectors
128
+
129
+ All selectors take an `AuthState` slice as argument (not the full Redux root state):
130
+
131
+ ```ts
132
+ selectAuthUser(state); // User | null
133
+ selectAuthStatus(state); // "pending" | "fulfilled"
134
+ selectAuthProviderType(state); // ProviderType | null
135
+ selectIsAuthenticated(state); // boolean
136
+ ```
137
+
138
+ ## Release process
139
+
140
+ 1. Bump `version` in `packages/core/package.json`
141
+ 2. Commit and push to `main`
142
+ 3. Create and push a git tag matching the new version:
143
+ ```bash
144
+ git tag packages/core@1.x.x
145
+ git push origin packages/core@1.x.x
146
+ ```
147
+ 4. The GitHub Actions publish workflow triggers automatically: it runs tests, builds the package, and publishes to npm.
148
+ 5. Consumers update via:
149
+ ```bash
150
+ pnpm add @uva-fnwi/datanose-core@latest
151
+ ```
@@ -0,0 +1,38 @@
1
+ import { User } from 'oidc-client-ts';
2
+ import { CanvasUserAndMetadata } from './Auth.types';
3
+ /**
4
+ * Fetch the data part contained within a JWT.
5
+ */
6
+ export declare const getDataFromToken: <T>(token: string) => T | null;
7
+ /**
8
+ * Check if a user is authenticated.
9
+ */
10
+ export declare const isUserAuthenticated: (user: User | null) => boolean;
11
+ /**
12
+ * Checks if the application is embedded.
13
+ */
14
+ export declare const isEmbedded: () => boolean;
15
+ /**
16
+ * Checks if the application is embedded in Canvas.
17
+ */
18
+ export declare const isEmbeddedInCanvas: () => boolean;
19
+ /**
20
+ * Takes a Canvas token and uses it to build a new User object and return additional metadata.
21
+ */
22
+ export declare const getUserAndMetadataForCanvasToken: (token: string) => CanvasUserAndMetadata;
23
+ /**
24
+ * Get the Canvas token from a separate section of the local storage (non-OIDC).
25
+ */
26
+ export declare const getCanvasTokenFromLocalStorage: () => string | null;
27
+ /**
28
+ * Set the Canvas token in a separate section of the local storage (non-OIDC).
29
+ */
30
+ export declare const setCanvasTokenInLocalStorage: (token: string) => void;
31
+ /**
32
+ * Convert the User object to a regular object so it can be stored.
33
+ */
34
+ export declare const convertUserToObject: (user: User | null) => User | null;
35
+ /**
36
+ * Checks if the current user is actually someone impersonating another user.
37
+ */
38
+ export declare const isImpersonating: (user: User | null) => boolean;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { User } from 'oidc-client-ts';
2
+ import { AuthState } from './Auth.state';
3
+ import { ProviderType } from './Auth.types';
4
+ type UnknownAction = {
5
+ type: string;
6
+ };
7
+ export type AuthAction = {
8
+ type: "auth/INITIALIZED" | "auth/USER_LOADED";
9
+ user: User | null;
10
+ providerType: ProviderType | null;
11
+ } | {
12
+ type: "auth/LOGOUT" | "auth/USER_UNLOADED" | "auth/LOGGING_OUT";
13
+ };
14
+ export declare function authReducer(state: AuthState | undefined, action: UnknownAction): AuthState;
15
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { AuthState } from './Auth.state';
2
+ export declare const selectAuthUser: (state: AuthState) => import('oidc-client-ts').User | null;
3
+ export declare const selectAuthStatus: (state: AuthState) => "pending" | "fulfilled";
4
+ export declare const selectAuthProviderType: (state: AuthState) => import('./Auth.types').ProviderType | null;
5
+ export declare const selectIsAuthenticated: (state: AuthState) => boolean;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { User } from 'oidc-client-ts';
2
+ import { ProviderType } from './Auth.types';
3
+ export type AuthState = {
4
+ user: User | null;
5
+ providerType: ProviderType | null;
6
+ isAuthenticated: boolean;
7
+ isLoading: boolean;
8
+ isLoggingOut: boolean;
9
+ };
10
+ export declare const DefaultAuthState: AuthState;
@@ -0,0 +1,60 @@
1
+ import { User } from 'oidc-client-ts';
2
+ import { AuthAction } from './Auth.reducer';
3
+ export type AuthConfig = {
4
+ authority: string;
5
+ clientId: string;
6
+ redirectUri: string;
7
+ logoutUri?: string;
8
+ };
9
+ export type ProviderType = "surf" | "canvas";
10
+ export type CustomUserState = {
11
+ redirectUrl: string;
12
+ } | undefined;
13
+ type JwtReservedClaims = {
14
+ exp: number;
15
+ iat: number;
16
+ iss: string;
17
+ nbf: number;
18
+ };
19
+ export type CanvasToken = JwtReservedClaims & {
20
+ familyname: string;
21
+ givenname: string;
22
+ iss: "lti";
23
+ name: string;
24
+ personid: number;
25
+ role: string;
26
+ target: string;
27
+ uvanetid: string;
28
+ locale: string;
29
+ };
30
+ export type DataNoseToken = JwtReservedClaims & {
31
+ clientid: string;
32
+ courseid: string;
33
+ familyname: string;
34
+ givenname: string;
35
+ impersonatedid: string;
36
+ iss: "dn";
37
+ name: string;
38
+ personid: string;
39
+ role: string;
40
+ sourcedid: string;
41
+ studentid: string;
42
+ uvanetid: string;
43
+ };
44
+ export type CanvasUserAndMetadata = {
45
+ user: User;
46
+ target: string;
47
+ locale: string;
48
+ };
49
+ export type AuthEventCallbacks = {
50
+ onAccessTokenExpired?: () => void;
51
+ onAccessTokenExpiring?: () => void;
52
+ onSilentRenewError?: (error: Error) => void;
53
+ onUserLoaded?: (user: User) => void;
54
+ onUserSessionChanged?: () => void;
55
+ onUserSignedIn?: () => void;
56
+ onUserSignedOut?: () => void;
57
+ onUserUnloaded?: () => void;
58
+ };
59
+ export type AuthStateChangeCallback = (action: AuthAction) => void;
60
+ export {};
@@ -0,0 +1,15 @@
1
+ import { User } from 'oidc-client-ts';
2
+ import { AuthState } from './Auth.state';
3
+ import { CanvasUserAndMetadata, CustomUserState } from './Auth.types';
4
+ export interface AuthContextProps extends AuthState {
5
+ surfStartLogin(state: CustomUserState): Promise<void>;
6
+ surfCompleteLogin(): Promise<User>;
7
+ canvasCompleteLogin(token: string): CanvasUserAndMetadata;
8
+ surfRenewLogin(state: CustomUserState): Promise<void>;
9
+ surfLogout(): Promise<void>;
10
+ impersonateSurfUser(accessToken: string): Promise<void>;
11
+ }
12
+ /**
13
+ * Context state to store auth data and expose auth functionality.
14
+ */
15
+ export declare const AuthContext: import('react').Context<AuthContextProps | undefined>;
@@ -0,0 +1,12 @@
1
+ import { default as React } from 'react';
2
+ import { AuthConfig, AuthEventCallbacks } from './Auth.types';
3
+ type AuthProviderProps = {
4
+ children?: React.ReactNode;
5
+ config: AuthConfig;
6
+ events: AuthEventCallbacks;
7
+ };
8
+ /**
9
+ * Wrapper component that initializes the authentication flow and creates an auth context that can be accessed through useAuth().
10
+ */
11
+ export declare const AuthProvider: (props: AuthProviderProps) => React.JSX.Element;
12
+ export {};
@@ -0,0 +1,20 @@
1
+ import { User } from 'oidc-client-ts';
2
+ import { AuthConfig, AuthEventCallbacks, AuthStateChangeCallback, CustomUserState } from './Auth.types';
3
+ declare class AuthService {
4
+ private initialized;
5
+ private loginInProgress;
6
+ private logoutUri?;
7
+ private updateState?;
8
+ private userManager;
9
+ constructor(config: AuthConfig);
10
+ initialize(updateState: AuthStateChangeCallback, events: AuthEventCallbacks): Promise<void>;
11
+ surfStartLogin: (state: CustomUserState) => Promise<void>;
12
+ surfCompleteLogin: () => Promise<User>;
13
+ canvasCompleteLogin: (token: string) => import('./Auth.types').CanvasUserAndMetadata;
14
+ surfRenewLogin: (state: CustomUserState) => Promise<void>;
15
+ surfLogout: () => Promise<void>;
16
+ impersonateSurfUser: (accessToken: string) => Promise<void>;
17
+ private initCanvasUser;
18
+ private initEvents;
19
+ }
20
+ export default AuthService;