Compare commits
7 Commits
0.14.0-bet
...
feat/open-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2bf6b8f91d | ||
![]() |
776c0fba8b | ||
![]() |
dd64aa2e79 | ||
![]() |
157b13baa7 | ||
![]() |
d1e284116d | ||
![]() |
2f9725d8e1 | ||
![]() |
ee7aea7bee |
30
app/package-lock.json
generated
30
app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "0.9.0-beta.3",
|
||||
"@authorizerdev/authorizer-react": "latest",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
@@ -24,9 +24,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@authorizerdev/authorizer-js": {
|
||||
"version": "0.4.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz",
|
||||
"integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==",
|
||||
"version": "0.4.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.3.tgz",
|
||||
"integrity": "sha512-OGZc6I6cnpi/WkSotkjVIc3LEzl8pFeiohr8+Db9xWd75/oTfOZqWRuIHTnTc1FC+6Sv2EjTJ9Aa6lrloWG+NQ==",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.1"
|
||||
},
|
||||
@@ -35,11 +35,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@authorizerdev/authorizer-react": {
|
||||
"version": "0.9.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.3.tgz",
|
||||
"integrity": "sha512-P93PW6W3Qm9BW3160gn0Ce+64UCFAOpoEOHf5537LgFPE8LpNAIU3EI6EtMNkOJS58pu1h2UkfyRyX/j0Pohjw==",
|
||||
"version": "0.9.0-beta.7",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.7.tgz",
|
||||
"integrity": "sha512-hCGsVionKMZNk+uD0CLtMIkUzhQqpHbVntko3rY+O7ouOrTrikY/WQVPbo1bqX1cu/6/cHE4RVU3cZ7V5xnxVg==",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-js": "^0.4.0-beta.0",
|
||||
"@authorizerdev/authorizer-js": "^0.4.0-beta.3",
|
||||
"final-form": "^4.20.2",
|
||||
"react-final-form": "^6.5.3",
|
||||
"styled-components": "^5.3.0"
|
||||
@@ -829,19 +829,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-js": {
|
||||
"version": "0.4.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz",
|
||||
"integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==",
|
||||
"version": "0.4.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.3.tgz",
|
||||
"integrity": "sha512-OGZc6I6cnpi/WkSotkjVIc3LEzl8pFeiohr8+Db9xWd75/oTfOZqWRuIHTnTc1FC+6Sv2EjTJ9Aa6lrloWG+NQ==",
|
||||
"requires": {
|
||||
"node-fetch": "^2.6.1"
|
||||
}
|
||||
},
|
||||
"@authorizerdev/authorizer-react": {
|
||||
"version": "0.9.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.3.tgz",
|
||||
"integrity": "sha512-P93PW6W3Qm9BW3160gn0Ce+64UCFAOpoEOHf5537LgFPE8LpNAIU3EI6EtMNkOJS58pu1h2UkfyRyX/j0Pohjw==",
|
||||
"version": "0.9.0-beta.7",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.7.tgz",
|
||||
"integrity": "sha512-hCGsVionKMZNk+uD0CLtMIkUzhQqpHbVntko3rY+O7ouOrTrikY/WQVPbo1bqX1cu/6/cHE4RVU3cZ7V5xnxVg==",
|
||||
"requires": {
|
||||
"@authorizerdev/authorizer-js": "^0.4.0-beta.0",
|
||||
"@authorizerdev/authorizer-js": "^0.4.0-beta.3",
|
||||
"final-form": "^4.20.2",
|
||||
"react-final-form": "^6.5.3",
|
||||
"styled-components": "^5.3.0"
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"author": "Lakhan Samani",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "0.9.0-beta.3",
|
||||
"@authorizerdev/authorizer-react": "latest",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
|
@@ -2,10 +2,33 @@ import React from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
|
||||
import Root from './Root';
|
||||
import { createRandomString } from './utils/common';
|
||||
|
||||
export default function App() {
|
||||
// @ts-ignore
|
||||
const globalState: Record<string, string> = window['__authorizer__'];
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const state = searchParams.get('state') || createRandomString();
|
||||
const scope = searchParams.get('scope')
|
||||
? searchParams.get('scope')?.toString().split(' ')
|
||||
: `openid profile email`;
|
||||
|
||||
const urlProps: Record<string, any> = {
|
||||
state,
|
||||
scope,
|
||||
};
|
||||
|
||||
const redirectURL =
|
||||
searchParams.get('redirect_uri') || searchParams.get('redirectURL');
|
||||
if (redirectURL) {
|
||||
urlProps.redirectURL = redirectURL;
|
||||
} else {
|
||||
urlProps.redirectURL = window.location.origin;
|
||||
}
|
||||
const globalState: Record<string, string> = {
|
||||
// @ts-ignore
|
||||
...window['__authorizer__'],
|
||||
...urlProps,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -38,7 +61,7 @@ export default function App() {
|
||||
redirectURL: globalState.redirectURL,
|
||||
}}
|
||||
>
|
||||
<Root />
|
||||
<Root globalState={globalState} />
|
||||
</AuthorizerProvider>
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
|
@@ -6,14 +6,20 @@ const ResetPassword = lazy(() => import('./pages/rest-password'));
|
||||
const Login = lazy(() => import('./pages/login'));
|
||||
const Dashboard = lazy(() => import('./pages/dashboard'));
|
||||
|
||||
export default function Root() {
|
||||
export default function Root({
|
||||
globalState,
|
||||
}: {
|
||||
globalState: Record<string, string>;
|
||||
}) {
|
||||
const { token, loading, config } = useAuthorizer();
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
console.log({ token });
|
||||
let redirectURL = config.redirectURL || '/app';
|
||||
const params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&refresh_token=${token.refresh_token}`;
|
||||
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
|
||||
if (token.refresh_token) {
|
||||
params += `&refresh_token=${token.refresh_token}`;
|
||||
}
|
||||
const url = new URL(redirectURL);
|
||||
if (redirectURL.includes('?')) {
|
||||
redirectURL = `${redirectURL}&${params}`;
|
||||
|
22
app/src/utils/common.ts
Normal file
22
app/src/utils/common.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export const getCrypto = () => {
|
||||
//ie 11.x uses msCrypto
|
||||
return (window.crypto || (window as any).msCrypto) as Crypto;
|
||||
};
|
||||
|
||||
export const createRandomString = () => {
|
||||
const charset =
|
||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
|
||||
let random = '';
|
||||
const randomValues = Array.from(
|
||||
getCrypto().getRandomValues(new Uint8Array(43))
|
||||
);
|
||||
randomValues.forEach((v) => (random += charset[v % charset.length]));
|
||||
return random;
|
||||
};
|
||||
|
||||
export const createQueryParams = (params: any) => {
|
||||
return Object.keys(params)
|
||||
.filter((k) => typeof params[k] !== 'undefined')
|
||||
.map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||
.join('&');
|
||||
};
|
@@ -29,10 +29,11 @@ import {
|
||||
} from 'react-icons/fi';
|
||||
import { IconType } from 'react-icons';
|
||||
import { ReactText } from 'react';
|
||||
import { useMutation } from 'urql';
|
||||
import { useMutation, useQuery } from 'urql';
|
||||
import { NavLink, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuthContext } from '../contexts/AuthContext';
|
||||
import { AdminLogout } from '../graphql/mutation';
|
||||
import { MetaQuery } from '../graphql/queries';
|
||||
|
||||
interface LinkItemProps {
|
||||
name: string;
|
||||
@@ -51,6 +52,7 @@ interface SidebarProps extends BoxProps {
|
||||
|
||||
export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
||||
const { pathname } = useLocation();
|
||||
const [{ fetching, data }] = useQuery({ query: MetaQuery });
|
||||
return (
|
||||
<Box
|
||||
transition="3s ease"
|
||||
@@ -98,6 +100,19 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
||||
>
|
||||
<NavItem icon={FiCode}>API Playground</NavItem>
|
||||
</Link>
|
||||
|
||||
{data?.meta?.version && (
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontSize="sm"
|
||||
textAlign="center"
|
||||
position="absolute"
|
||||
bottom="5"
|
||||
left="7"
|
||||
>
|
||||
Current Version: {data.meta.version}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -1,3 +1,12 @@
|
||||
export const MetaQuery = `
|
||||
query MetaQuery {
|
||||
meta {
|
||||
version
|
||||
client_id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const AdminSessionQuery = `
|
||||
query {
|
||||
_admin_session{
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { Box, Center, Flex, Image, Text } from '@chakra-ui/react';
|
||||
import { Box, Flex, Image, Text, Spinner } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { LOGO_URL } from '../constants';
|
||||
import { useQuery } from 'urql';
|
||||
import { MetaQuery } from '../graphql/queries';
|
||||
|
||||
export function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
const [{ fetching, data }] = useQuery({ query: MetaQuery });
|
||||
return (
|
||||
<Flex
|
||||
flexWrap="wrap"
|
||||
@@ -23,9 +25,18 @@ export function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Box p="6" m="5" rounded="5" bg="white" w="500px" shadow="xl">
|
||||
{children}
|
||||
</Box>
|
||||
{fetching ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<Box p="6" m="5" rounded="5" bg="white" w="500px" shadow="xl">
|
||||
{children}
|
||||
</Box>
|
||||
<Text color="gray.600" fontSize="sm">
|
||||
Current Version: {data.meta.version}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import {
|
||||
useToast,
|
||||
VStack,
|
||||
Text,
|
||||
Divider,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useMutation } from 'urql';
|
||||
|
@@ -12,7 +12,7 @@ type VerificationRequest struct {
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
|
||||
Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"`
|
||||
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce"`
|
||||
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri"`
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
||||
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||
result := p.db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
|
||||
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at", "nonce", "redirect_uri"}),
|
||||
}).Create(&verificationRequest)
|
||||
|
||||
if result.Error != nil {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
)
|
||||
@@ -103,5 +105,9 @@ func SendVerificationMail(toEmail, token, hostname string) error {
|
||||
message = addEmailTemplate(message, data, "verify_email.tmpl")
|
||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
||||
|
||||
return SendMail(Receiver, Subject, message)
|
||||
err := SendMail(Receiver, Subject, message)
|
||||
if err != nil {
|
||||
log.Println("=> error sending email:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@@ -1343,6 +1343,7 @@ input SignUpInput {
|
||||
password: String!
|
||||
confirm_password: String!
|
||||
roles: [String!]
|
||||
scope: [String!]
|
||||
}
|
||||
|
||||
input LoginInput {
|
||||
@@ -7298,6 +7299,14 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "scope":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
|
||||
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -153,6 +153,7 @@ type SignUpInput struct {
|
||||
Password string `json:"password"`
|
||||
ConfirmPassword string `json:"confirm_password"`
|
||||
Roles []string `json:"roles"`
|
||||
Scope []string `json:"scope"`
|
||||
}
|
||||
|
||||
type UpdateEnvInput struct {
|
||||
|
@@ -181,6 +181,7 @@ input SignUpInput {
|
||||
password: String!
|
||||
confirm_password: String!
|
||||
roles: [String!]
|
||||
scope: [String!]
|
||||
}
|
||||
|
||||
input LoginInput {
|
||||
|
@@ -109,7 +109,5 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
|
||||
// Query returns generated.QueryResolver implementation.
|
||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
type (
|
||||
mutationResolver struct{ *Resolver }
|
||||
queryResolver struct{ *Resolver }
|
||||
)
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
|
@@ -95,7 +95,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
if redirectURL == "" {
|
||||
redirectURL = claim["redirect_url"].(string)
|
||||
redirectURL = claim["redirect_uri"].(string)
|
||||
}
|
||||
|
||||
if strings.Contains(redirectURL, "?") {
|
||||
|
@@ -139,7 +139,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
||||
if err != nil {
|
||||
log.Println(`error generating token`, err)
|
||||
}
|
||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||
_, err = db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||
Token: verificationToken,
|
||||
Identifier: verificationType,
|
||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||
@@ -147,8 +147,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
||||
Nonce: nonceHash,
|
||||
RedirectURI: redirectURL,
|
||||
})
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// exec it as go routin so that we can reduce the api latency
|
||||
// exec it as go routing so that we can reduce the api latency
|
||||
go email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||
}
|
||||
|
||||
|
@@ -151,6 +151,9 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
||||
}
|
||||
} else {
|
||||
scope := []string{"openid", "email", "profile"}
|
||||
if params.Scope != nil && len(scope) > 0 {
|
||||
scope = params.Scope
|
||||
}
|
||||
|
||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
|
||||
if err != nil {
|
||||
|
@@ -2,7 +2,6 @@ package token
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
@@ -92,7 +91,6 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error
|
||||
return claims, errors.New("invalid audience")
|
||||
}
|
||||
|
||||
fmt.Println("claims:", claims, claims["nonce"], nonce)
|
||||
if claims["nonce"] != nonce {
|
||||
return claims, errors.New("invalid nonce")
|
||||
}
|
||||
|
Reference in New Issue
Block a user