uibee 1.5.0 → 1.6.1

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,2 @@
1
+ import type { AuthCallbackProps } from 'uibee/utils';
2
+ export default function authCallback({ req, tokenURL, clientID, clientSecret, redirectURI, userInfoURL, tokenRedirectURL }: AuthCallbackProps): Promise<Response>;
@@ -0,0 +1,60 @@
1
+ import { NextResponse } from 'next/server';
2
+ export default async function authCallback({ req, tokenURL, clientID, clientSecret, redirectURI, userInfoURL, tokenRedirectURL }) {
3
+ const searchParams = new URL(req.url).searchParams;
4
+ if (!searchParams) {
5
+ return NextResponse.json({ error: 'No search parameters found.' }, { status: 400 });
6
+ }
7
+ const code = searchParams.get('code');
8
+ if (!code) {
9
+ return NextResponse.json({ error: 'No authorization code found.' }, { status: 400 });
10
+ }
11
+ try {
12
+ // Exchanges callback code for access token
13
+ const tokenResponse = await fetch(tokenURL, {
14
+ method: 'POST',
15
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
16
+ body: new URLSearchParams({
17
+ client_id: clientID,
18
+ client_secret: clientSecret,
19
+ code: code,
20
+ redirect_uri: redirectURI,
21
+ grant_type: 'authorization_code',
22
+ }).toString()
23
+ });
24
+ const tokenResponseBody = await tokenResponse.text();
25
+ if (!tokenResponse.ok) {
26
+ return new Response(JSON.stringify(`Failed to obtain token: ${tokenResponseBody}`), {
27
+ status: 500,
28
+ headers: { 'Content-Type': 'application/json' }
29
+ });
30
+ }
31
+ const token = JSON.parse(tokenResponseBody);
32
+ // Fetches user info using access token
33
+ const userInfoResponse = await fetch(userInfoURL, {
34
+ headers: { Authorization: `Bearer ${token.access_token}` }
35
+ });
36
+ if (!userInfoResponse.ok) {
37
+ const userInfoError = await userInfoResponse.text();
38
+ return new Response(`No user info found: ${userInfoError}`, {
39
+ status: 500,
40
+ headers: { 'Content-Type': 'application/json' }
41
+ });
42
+ }
43
+ const userInfo = await userInfoResponse.json();
44
+ const redirectUrl = new URL(tokenRedirectURL);
45
+ const params = new URLSearchParams({
46
+ id: userInfo.sub,
47
+ name: userInfo.name,
48
+ email: userInfo.email,
49
+ groups: userInfo.groups.join(','),
50
+ access_token: token.access_token,
51
+ });
52
+ redirectUrl.search = params.toString();
53
+ return NextResponse.redirect(redirectUrl.toString());
54
+ }
55
+ catch (err) {
56
+ const error = err;
57
+ console.error('Error during OAuth flow:', error.message);
58
+ return NextResponse.json({ error: 'Authentication failed' }, { status: 500 });
59
+ }
60
+ }
@@ -0,0 +1,3 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { AuthLoginProps } from 'uibee/utils';
3
+ export default function AuthLogin({ clientID, redirectURI, authURL }: AuthLoginProps): Promise<NextResponse<unknown>>;
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from 'next/server';
2
+ export default async function AuthLogin({ clientID, redirectURI, authURL }) {
3
+ const state = Math.random().toString(36).substring(5);
4
+ const authQueryParams = new URLSearchParams({
5
+ client_id: clientID,
6
+ redirect_uri: redirectURI,
7
+ response_type: 'code',
8
+ scope: 'openid profile email',
9
+ state: state,
10
+ }).toString();
11
+ return NextResponse.redirect(`${authURL}?${authQueryParams}`);
12
+ }
@@ -0,0 +1,3 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { AuthLogoutProps } from 'uibee/utils';
3
+ export default function AuthLogout({ frontendURL }: AuthLogoutProps): Promise<NextResponse<unknown>>;
@@ -0,0 +1,18 @@
1
+ import { NextResponse } from 'next/server';
2
+ export default async function AuthLogout({ frontendURL }) {
3
+ const response = NextResponse.redirect(new URL('/', frontendURL));
4
+ // Remove all authentication cookies
5
+ const cookiesToRemove = [
6
+ 'access_token',
7
+ 'access_token_expires',
8
+ 'refresh_token',
9
+ 'refresh_token_expires',
10
+ 'user_id',
11
+ 'user_name',
12
+ 'user_roles'
13
+ ];
14
+ cookiesToRemove.forEach(cookieName => {
15
+ response.cookies.delete(cookieName);
16
+ });
17
+ return response;
18
+ }
@@ -0,0 +1,3 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { AuthTokenProps } from 'uibee/utils';
3
+ export default function AuthToken({ request, frontendURL }: AuthTokenProps): Promise<NextResponse<unknown>>;
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server';
2
+ export default async function AuthToken({ request, frontendURL }) {
3
+ const url = new URL(request.url);
4
+ const token = url.searchParams.get('access_token');
5
+ const btg = url.searchParams.get('btg');
6
+ if (!token) {
7
+ return NextResponse.json({ error: 'No access token provided' }, { status: 400 });
8
+ }
9
+ if (btg) {
10
+ return NextResponse.redirect(new URL('/dashboard', frontendURL));
11
+ }
12
+ const accessToken = url.searchParams.get('access_token');
13
+ const accessTokenExpires = url.searchParams.get('access_token_expires');
14
+ const refreshToken = url.searchParams.get('refresh_token');
15
+ const refreshTokenExpires = url.searchParams.get('refresh_token_expires');
16
+ const userId = url.searchParams.get('user_id');
17
+ const userName = url.searchParams.get('user_name');
18
+ const userRoles = url.searchParams.get('user_roles');
19
+ const response = NextResponse.redirect(new URL('/dashboard', frontendURL));
20
+ response.cookies.set('access_token', accessToken);
21
+ response.cookies.set('access_token_expires', accessTokenExpires);
22
+ response.cookies.set('refresh_token', refreshToken);
23
+ response.cookies.set('refresh_token_expires', refreshTokenExpires);
24
+ response.cookies.set('user_id', userId);
25
+ response.cookies.set('user_name', userName);
26
+ response.cookies.set('user_roles', userRoles);
27
+ return response;
28
+ }
@@ -2,3 +2,7 @@ export { default as alertSlowQuery } from './sql/alertSlowQuery';
2
2
  export { default as discordAlert } from './discord/discordAlert';
3
3
  export { getCookie, setCookie, removeCookie } from './cookies/cookies';
4
4
  export { LogoConsoleOutput } from './LogoConsoleOutput/LogoConsoleOutput';
5
+ export { default as authLogin } from './auth/login';
6
+ export { default as authCallback } from './auth/callback';
7
+ export { default as authToken } from './auth/token';
8
+ export { default as authLogout } from './auth/logout';
@@ -2,3 +2,8 @@ export { default as alertSlowQuery } from './sql/alertSlowQuery';
2
2
  export { default as discordAlert } from './discord/discordAlert';
3
3
  export { getCookie, setCookie, removeCookie } from './cookies/cookies';
4
4
  export { LogoConsoleOutput } from './LogoConsoleOutput/LogoConsoleOutput';
5
+ // Auth
6
+ export { default as authLogin } from './auth/login';
7
+ export { default as authCallback } from './auth/callback';
8
+ export { default as authToken } from './auth/token';
9
+ export { default as authLogout } from './auth/logout';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Shared components, functions and hooks for reuse across Login projects",
5
5
  "homepage": "https://github.com/Login-Linjeforening-for-IT/uibee#readme",
6
6
  "bugs": {
@@ -1,4 +1,6 @@
1
1
  /* eslint-disable @stylistic/semi */
2
+ import { NextRequest } from 'next/server'
3
+
2
4
  declare module 'uibee/utils' {
3
5
  export interface SlowQueryProps {
4
6
  application: string
@@ -29,6 +31,35 @@ declare module 'uibee/utils' {
29
31
  webhookURL: string
30
32
  }
31
33
 
34
+ export interface AuthLoginProps {
35
+ clientID: string
36
+ redirectURI: string
37
+ authURL: string
38
+ }
39
+
40
+ export interface AuthCallbackProps {
41
+ req: Request
42
+ tokenURL: string
43
+ clientID: string
44
+ clientSecret: string
45
+ redirectURI: string
46
+ userInfoURL: string
47
+ tokenRedirectURL: string
48
+ }
49
+
50
+ export interface AuthTokenProps {
51
+ request: NextRequest
52
+ frontendURL: string
53
+ }
54
+ export interface AuthLogoutProps {
55
+ request: NextRequest
56
+ frontendURL: string
57
+ }
58
+
32
59
  export default async function alertSlowQuery(props: SlowQueryProps): Promise<void>;
33
60
  export default async function discordAlert(props: DiscordAlertProps): Promise<number>;
61
+ export default async function authLogin(props: AuthLoginProps): Promise<Response>;
62
+ export default async function authCallback(props: AuthCallbackProps): Promise<Response>;
63
+ export default async function authToken(props: AuthTokenProps): Promise<Response>;
64
+ export default async function authLogout(props: AuthLogoutProps): Promise<Response>;
34
65
  }
@@ -0,0 +1,87 @@
1
+ import { NextResponse } from 'next/server'
2
+ import type { AuthCallbackProps } from 'uibee/utils'
3
+
4
+ type UserInfo = {
5
+ sub: string
6
+ name: string
7
+ email: string
8
+ groups: string[]
9
+ }
10
+
11
+ export default async function authCallback({
12
+ req,
13
+ tokenURL,
14
+ clientID,
15
+ clientSecret,
16
+ redirectURI,
17
+ userInfoURL,
18
+ tokenRedirectURL
19
+ }: AuthCallbackProps) {
20
+ const searchParams = new URL(req.url).searchParams
21
+
22
+ if (!searchParams) {
23
+ return NextResponse.json({ error: 'No search parameters found.' }, { status: 400 })
24
+ }
25
+ const code = searchParams.get('code')
26
+
27
+ if (!code) {
28
+ return NextResponse.json({ error: 'No authorization code found.' }, { status: 400 })
29
+ }
30
+
31
+ try {
32
+ // Exchanges callback code for access token
33
+ const tokenResponse = await fetch(tokenURL, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
36
+ body: new URLSearchParams({
37
+ client_id: clientID,
38
+ client_secret: clientSecret,
39
+ code: code as string,
40
+ redirect_uri: redirectURI,
41
+ grant_type: 'authorization_code',
42
+ }).toString()
43
+ })
44
+
45
+ const tokenResponseBody = await tokenResponse.text()
46
+
47
+ if (!tokenResponse.ok) {
48
+ return new Response(JSON.stringify(`Failed to obtain token: ${tokenResponseBody}`), {
49
+ status: 500,
50
+ headers: { 'Content-Type': 'application/json' }
51
+ })
52
+ }
53
+
54
+ const token = JSON.parse(tokenResponseBody)
55
+
56
+ // Fetches user info using access token
57
+ const userInfoResponse = await fetch(userInfoURL, {
58
+ headers: { Authorization: `Bearer ${token.access_token}` }
59
+ })
60
+
61
+ if (!userInfoResponse.ok) {
62
+ const userInfoError = await userInfoResponse.text()
63
+ return new Response(`No user info found: ${userInfoError}`, {
64
+ status: 500,
65
+ headers: { 'Content-Type': 'application/json' }
66
+ })
67
+ }
68
+
69
+ const userInfo = await userInfoResponse.json() as UserInfo
70
+
71
+ const redirectUrl = new URL(tokenRedirectURL)
72
+ const params = new URLSearchParams({
73
+ id: userInfo.sub,
74
+ name: userInfo.name,
75
+ email: userInfo.email,
76
+ groups: userInfo.groups.join(','),
77
+ access_token: token.access_token,
78
+ })
79
+
80
+ redirectUrl.search = params.toString()
81
+ return NextResponse.redirect(redirectUrl.toString())
82
+ } catch (err: unknown) {
83
+ const error = err as Error
84
+ console.error('Error during OAuth flow:', error.message)
85
+ return NextResponse.json({ error: 'Authentication failed' }, { status: 500 })
86
+ }
87
+ }
@@ -0,0 +1,17 @@
1
+ import { NextResponse } from 'next/server'
2
+ import type { AuthLoginProps } from 'uibee/utils'
3
+
4
+ export default async function AuthLogin({ clientID, redirectURI, authURL }: AuthLoginProps) {
5
+ const state = Math.random().toString(36).substring(5)
6
+ const authQueryParams = new URLSearchParams({
7
+ client_id: clientID,
8
+ redirect_uri: redirectURI,
9
+ response_type: 'code',
10
+ scope: 'openid profile email',
11
+ state: state,
12
+ }).toString()
13
+
14
+ return NextResponse.redirect(
15
+ `${authURL}?${authQueryParams}`
16
+ )
17
+ }
@@ -0,0 +1,23 @@
1
+ import { NextResponse } from 'next/server'
2
+ import type { AuthLogoutProps } from 'uibee/utils'
3
+
4
+ export default async function AuthLogout({ frontendURL }: AuthLogoutProps) {
5
+ const response = NextResponse.redirect(new URL('/', frontendURL))
6
+
7
+ // Remove all authentication cookies
8
+ const cookiesToRemove = [
9
+ 'access_token',
10
+ 'access_token_expires',
11
+ 'refresh_token',
12
+ 'refresh_token_expires',
13
+ 'user_id',
14
+ 'user_name',
15
+ 'user_roles'
16
+ ]
17
+
18
+ cookiesToRemove.forEach(cookieName => {
19
+ response.cookies.delete(cookieName)
20
+ })
21
+
22
+ return response
23
+ }
@@ -0,0 +1,34 @@
1
+ import { NextResponse } from 'next/server'
2
+ import type { AuthTokenProps } from 'uibee/utils'
3
+
4
+ export default async function AuthToken({ request, frontendURL }: AuthTokenProps) {
5
+ const url = new URL(request.url)
6
+ const token = url.searchParams.get('access_token')
7
+ const btg = url.searchParams.get('btg')
8
+ if (!token) {
9
+ return NextResponse.json({ error: 'No access token provided' }, { status: 400 })
10
+ }
11
+
12
+ if (btg) {
13
+ return NextResponse.redirect(new URL('/dashboard', frontendURL))
14
+ }
15
+
16
+ const accessToken = url.searchParams.get('access_token')!
17
+ const accessTokenExpires = url.searchParams.get('access_token_expires')!
18
+ const refreshToken = url.searchParams.get('refresh_token')!
19
+ const refreshTokenExpires = url.searchParams.get('refresh_token_expires')!
20
+ const userId = url.searchParams.get('user_id')!
21
+ const userName = url.searchParams.get('user_name')!
22
+ const userRoles = url.searchParams.get('user_roles')!
23
+
24
+ const response = NextResponse.redirect(new URL('/dashboard', frontendURL))
25
+ response.cookies.set('access_token', accessToken)
26
+ response.cookies.set('access_token_expires', accessTokenExpires)
27
+ response.cookies.set('refresh_token', refreshToken)
28
+ response.cookies.set('refresh_token_expires', refreshTokenExpires)
29
+ response.cookies.set('user_id', userId)
30
+ response.cookies.set('user_name', userName)
31
+ response.cookies.set('user_roles', userRoles)
32
+
33
+ return response
34
+ }
@@ -1,4 +1,10 @@
1
1
  export { default as alertSlowQuery } from './sql/alertSlowQuery'
2
2
  export { default as discordAlert } from './discord/discordAlert'
3
3
  export { getCookie, setCookie, removeCookie } from './cookies/cookies'
4
- export { LogoConsoleOutput } from './LogoConsoleOutput/LogoConsoleOutput'
4
+ export { LogoConsoleOutput } from './LogoConsoleOutput/LogoConsoleOutput'
5
+
6
+ // Auth
7
+ export { default as authLogin } from './auth/login'
8
+ export { default as authCallback } from './auth/callback'
9
+ export { default as authToken } from './auth/token'
10
+ export { default as authLogout } from './auth/logout'
package/tsconfig.json CHANGED
@@ -6,7 +6,6 @@
6
6
  "declaration": true,
7
7
  "outDir": "dist",
8
8
  "rootDir": ".",
9
- "baseUrl": ".",
10
9
  "esModuleInterop": true,
11
10
  "forceConsistentCasingInFileNames": true,
12
11
  "strict": true,
@@ -15,7 +14,7 @@
15
14
  "resolveJsonModule": true,
16
15
  "paths": {
17
16
  "@images/*": [
18
- "images/*"
17
+ "./images/*"
19
18
  ]
20
19
  }
21
20
  },