Spaces:
Build error
Build error
File size: 6,279 Bytes
0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 b4297ca 0702eb8 d5e0a0f 0702eb8 b4297ca d5e0a0f b4297ca d5e0a0f b4297ca d5e0a0f b4297ca 0702eb8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
import { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { SupabaseAdapter } from "@auth/supabase-adapter";
import type { Adapter } from 'next-auth/adapters';
import jwt from "jsonwebtoken";
import { JWE, JWK } from 'node-jose';
const makeUserinfoRequest = async (context: any) => {
console.log('context:', context);
const response = await fetch("https://api.id.gov.sg/v2/oauth/userinfo", {
headers: {
Authorization: `Bearer ${context.tokens.access_token}`,
},
})
const profile = await response.json()
// Decrypt the encrypted profile data
const privateKey = process.env.SGID_PRIVATE_KEY as string;
const decryptedProfile = await decryptData(profile.key, profile.data, privateKey);
// Build profile object
let newProfile = {
sub: profile.sub,
name: decryptedProfile['myinfo.name'],
}
// Return the decrypted profile
return newProfile
}
const decryptData = async (encKey: string, block: { [s: string]: unknown; } | ArrayLike<unknown>, privateKeyPem: string | object | Buffer | JWK.RawKey) => {
const result: { [key: string]: string } = {};
// Decrypted encKey to get block key
const privateKey = await JWK.asKey(privateKeyPem, 'pem');
const key = await JWE.createDecrypt(privateKey).decrypt(encKey);
// Parse the block key
const decryptedKey = await JWK.asKey(key.plaintext, 'json');
// Decrypt data
for (const [key, value] of Object.entries(block)) {
const { plaintext } = await JWE.createDecrypt(decryptedKey).decrypt(value as string);
result[key] = plaintext.toString('ascii');
}
return result
}
// You'll need to import and pass this
// to `NextAuth` in `app/api/auth/[...nextauth]/route.ts`
export const config = {
// Configure one or more authentication providers
// Enable debug messages if running in development
debug: process.env.NODE_ENV === 'development',
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
{
id: 'sgid',
name: 'SGID',
type: 'oauth',
issuer: 'https://api.id.gov.sg/v2',
authorization: {
url: 'https://api.id.gov.sg/v2/oauth/authorize',
params: { scope: 'openid myinfo.name' },
},
jwks_endpoint: 'https://api.id.gov.sg/v2/.well-known/jwks.json',
token: 'https://api.id.gov.sg/v2/oauth/token',
userinfo: {
url: 'https://api.id.gov.sg/v2/oauth/userinfo',
// The result of this method will be the input to the `profile` callback.
async request(context) {
// context contains useful properties to help you make the request.
return await makeUserinfoRequest(context)
}
},
checks: ['pkce', 'state'],
idToken: true,
client: { token_endpoint_auth_method: 'client_secret_post' },
clientId: process.env.SGID_CLIENT_ID,
clientSecret: process.env.SGID_CLIENT_SECRET,
profile: async (profile) => {
return {
id: profile.sub,
name: profile.name,
}
},
},
],
// Persist accounts and session state to Supabase
adapter: SupabaseAdapter({
url: process.env.SUPABASE_URL || '',
secret: process.env.SUPABASE_SERVICE_ROLE_KEY || '',
}) as Adapter,
pages: {
signIn: '/sign-in',
},
session: {
// Choose how you want to save the user session.
// The default is `"jwt"`, an encrypted JWT (JWE) stored in the session cookie.
// If you use an `adapter` however, we default it to `"database"` instead.
// You can still force a JWT session by explicitly defining `"jwt"`.
// When using `"database"`, the session cookie will only contain a `sessionToken` value,
// which is used to look up the session in the database.
// strategy: "jwt",
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 30 * 24 * 60 * 60, // 30 days
// Seconds - Throttle how frequently to write to database to extend a session.
// Use it to limit write operations. Set to 0 to always update the database.
// Note: This option is ignored if using JSON Web Tokens
updateAge: 24 * 60 * 60, // 24 hours
},
callbacks: {
async jwt({ token, user, account, profile }) {
// Persist the OAuth access_token and or the user id to the token right after signin
if (account) {
token.accessToken = account.access_token
token.id = profile?.sub
}
return token;
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
const signingSecret = process.env.SUPABASE_JWT_SECRET
// console.log('Signing Secret:', signingSecret);
if (signingSecret) {
const payload = {
aud: "authenticated",
exp: Math.floor(new Date(session.expires).getTime() / 1000),
sub: user.id,
// email: user.email,
role: "authenticated",
}
session.supabaseAccessToken = jwt.sign(payload, signingSecret) as string;
// console.log('New Session:', session);
// session.jwt = token.jwt as string;
// session.id = token.id as string;
}
return session;
},
}
} satisfies NextAuthOptions
// Use it in server contexts
export async function auth(...args: [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]] | [NextApiRequest, NextApiResponse] | []) {
return getServerSession(...args, config)
} |